Merge pull request #15 from Qstick/feature/UIWork
Rename AddArtist, Artist UI folders. Other UI Workpull/6/head
commit
da898cfd84
@ -0,0 +1,23 @@
|
||||
var Backbone = require('backbone');
|
||||
var ArtistModel = require('../Artist/ArtistModel');
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/artist/lookup',
|
||||
model : ArtistModel,
|
||||
|
||||
parse : function(response) {
|
||||
var self = this;
|
||||
|
||||
_.each(response, function(model) {
|
||||
model.id = undefined;
|
||||
|
||||
if (self.unmappedFolderModel) {
|
||||
model.path = self.unmappedFolderModel.get('folder').path;
|
||||
}
|
||||
});
|
||||
console.log('response: ', response);
|
||||
|
||||
return response;
|
||||
}
|
||||
});
|
@ -0,0 +1,53 @@
|
||||
var vent = require('vent');
|
||||
var AppLayout = require('../AppLayout');
|
||||
var Marionette = require('marionette');
|
||||
var RootFolderLayout = require('./RootFolders/RootFolderLayout');
|
||||
var ExistingArtistCollectionView = require('./Existing/AddExistingArtistCollectionView');
|
||||
var AddArtistView = require('./AddArtistView');
|
||||
var ProfileCollection = require('../Profile/ProfileCollection');
|
||||
var RootFolderCollection = require('./RootFolders/RootFolderCollection');
|
||||
require('../Artist/ArtistCollection');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'AddArtist/AddArtistLayoutTemplate',
|
||||
|
||||
regions : {
|
||||
workspace : '#add-artist-workspace'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-import' : '_importArtist',
|
||||
'click .x-add-new' : '_addArtist'
|
||||
},
|
||||
|
||||
attributes : {
|
||||
id : 'add-artist-screen'
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
ProfileCollection.fetch();
|
||||
RootFolderCollection.fetch().done(function() {
|
||||
RootFolderCollection.synced = true;
|
||||
});
|
||||
},
|
||||
|
||||
onShow : function() {
|
||||
this.workspace.show(new AddArtistView());
|
||||
},
|
||||
|
||||
_folderSelected : function(options) {
|
||||
vent.trigger(vent.Commands.CloseModalCommand);
|
||||
|
||||
this.workspace.show(new ExistingArtistCollectionView({ model : options.model }));
|
||||
},
|
||||
|
||||
_importArtist : function() {
|
||||
this.rootFolderLayout = new RootFolderLayout();
|
||||
this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected);
|
||||
AppLayout.modalRegion.show(this.rootFolderLayout);
|
||||
},
|
||||
|
||||
_addArtist : function() {
|
||||
this.workspace.show(new AddArtistView());
|
||||
}
|
||||
});
|
@ -0,0 +1,17 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="btn-group add-artist-btn-group btn-group-lg btn-block">
|
||||
<button type="button" class="btn btn-default col-md-10 col-xs-8 add-artist-import-btn x-import">
|
||||
<i class="icon-lidarr-hdd"/>
|
||||
Import existing artists on disk
|
||||
</button>
|
||||
<button class="btn btn-default col-md-2 col-xs-4 x-add-new"><i class="icon-lidarr-active hidden-xs"></i> Add New Artist</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="add-artist-workspace"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,183 @@
|
||||
var _ = require('underscore');
|
||||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
var AddArtistCollection = require('./AddArtistCollection');
|
||||
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 : 'AddArtist/AddArtistViewTemplate',
|
||||
|
||||
regions : {
|
||||
searchResult : '#search-result'
|
||||
},
|
||||
|
||||
ui : {
|
||||
artistSearch : '.x-artist-search',
|
||||
searchBar : '.x-search-bar',
|
||||
loadMore : '.x-load-more'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-load-more' : '_onLoadMore'
|
||||
},
|
||||
|
||||
initialize : function(options) {
|
||||
this.isExisting = options.isExisting;
|
||||
this.collection = new AddArtistCollection();
|
||||
console.log('this.collection:', this.collection);
|
||||
|
||||
if (this.isExisting) {
|
||||
this.collection.unmappedFolderModel = this.model;
|
||||
}
|
||||
|
||||
if (this.isExisting) {
|
||||
this.className = 'existing-artist';
|
||||
} else {
|
||||
this.className = 'new-artist';
|
||||
}
|
||||
|
||||
this.listenTo(vent, vent.Events.ArtistAdded, this._onArtistAdded);
|
||||
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.artistSearch.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.artistSearch.val()
|
||||
});
|
||||
});
|
||||
|
||||
this._clearResults();
|
||||
|
||||
if (this.isExisting) {
|
||||
this.ui.searchBar.hide();
|
||||
}
|
||||
},
|
||||
|
||||
onShow : function() {
|
||||
this.ui.artistSearch.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;
|
||||
},
|
||||
|
||||
_onArtistAdded : function(options) {
|
||||
if (this.isExisting && options.artist.get('path') === this.model.get('folder').path) {
|
||||
this.close();
|
||||
}
|
||||
|
||||
else if (!this.isExisting) {
|
||||
this.collection.term = '';
|
||||
this.collection.reset();
|
||||
this._clearResults();
|
||||
this.ui.artistSearch.val('');
|
||||
this.ui.artistSearch.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-artist-search">
|
||||
<span class="input-group-addon"><i class="icon-lidarr-search"/></span>
|
||||
|
||||
{{#if folder}}
|
||||
<input type="text" class="form-control x-artist-search" value="{{folder.name}}">
|
||||
{{else}}
|
||||
<input type="text" class="form-control x-artist-search" placeholder="Start typing the name of an artist or album...">
|
||||
{{/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-artist-loadmore x-load-more" style="display: none;">
|
||||
<i class="icon-lidarr-load-more"/>
|
||||
more
|
||||
</div>
|
@ -0,0 +1,3 @@
|
||||
<select class="form-control col-md-2 x-artist-type" name="artistType">
|
||||
<option value="standard">Standard</option>
|
||||
</select>
|
@ -0,0 +1,5 @@
|
||||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
template : 'AddArtist/EmptyViewTemplate'
|
||||
});
|
@ -0,0 +1,3 @@
|
||||
<div class="text-center hint col-md-12">
|
||||
<span>You can also search by Spotify using the spotify: prefixes.</span>
|
||||
</div>
|
@ -0,0 +1,13 @@
|
||||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
template : 'AddArtist/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 artist name contains non-alphanumeric characters try removing them, otherwise try your search again later.
|
||||
</div>
|
@ -0,0 +1,51 @@
|
||||
var Marionette = require('marionette');
|
||||
var AddArtistView = require('../AddArtistView');
|
||||
var UnmappedFolderCollection = require('./UnmappedFolderCollection');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
itemView : AddArtistView,
|
||||
itemViewContainer : '.x-loading-folders',
|
||||
template : 'AddArtist/Existing/AddExistingArtistCollectionViewTemplate',
|
||||
|
||||
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 server for your artists, 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 tracks except specials</dd>
|
||||
<dt>Future</dt>
|
||||
<dd>Monitor tracks that have not been released yet</dd>
|
||||
<dt>Missing</dt>
|
||||
<dd>Monitor tracks that do not have files or have not aired yet</dd>
|
||||
<dt>Existing</dt>
|
||||
<dd>Monitor tracks that have files or have not aired yet</dd>
|
||||
<dt>First Season</dt>
|
||||
<dd>Monitor all tracks of the first album. All other albums will be ignored</dd>
|
||||
<dt>Latest Season</dt>
|
||||
<dd>Monitor all tracks of the latest album and future albums</dd>
|
||||
<dt>None</dt>
|
||||
<dd>No tracks will be monitored.</dd>
|
||||
<!--<dt>Latest Season</dt>-->
|
||||
<!--<dd>Monitor all tracks the latest album only, previous albums will be ignored</dd>-->
|
||||
</dl>
|
@ -0,0 +1,13 @@
|
||||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
template : 'AddArtist/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 artist matching '{{term}}'
|
||||
</h3>
|
||||
<a href="https://github.com/mattman86/Lidarr/wiki/FAQ#wiki-why-cant-i-add-a-new-show-to-nzbdrone-its-on-thetvdb">Why can't I find my artist?</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 : 'AddArtist/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 : 'AddArtist/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-lidarr-delete x-delete"></i>
|
||||
</td>
|
@ -0,0 +1,80 @@
|
||||
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 : 'AddArtist/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 = null;
|
||||
},
|
||||
|
||||
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() {
|
||||
if (!this.rootfolderListView) {
|
||||
this.rootfolderListView = new RootFolderCollectionView({ collection : RootFolderCollection });
|
||||
this.currentDirs.show(this.rootfolderListView);
|
||||
|
||||
this.listenTo(this.rootfolderListView, 'itemview:folderSelected', this._onFolderSelected);
|
||||
}
|
||||
},
|
||||
|
||||
_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,297 @@
|
||||
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 ArtistCollection = require('../Artist/ArtistCollection');
|
||||
var Config = require('../Config');
|
||||
var Messenger = require('../Shared/Messenger');
|
||||
var AsValidatedView = require('../Mixins/AsValidatedView');
|
||||
|
||||
require('jquery.dotdotdot');
|
||||
|
||||
var view = Marionette.ItemView.extend({
|
||||
|
||||
template : 'AddArtist/SearchResultViewTemplate',
|
||||
|
||||
ui : {
|
||||
profile : '.x-profile',
|
||||
rootFolder : '.x-root-folder',
|
||||
albumFolder : '.x-album-folder',
|
||||
artistType : '.x-artist-type',
|
||||
monitor : '.x-monitor',
|
||||
monitorTooltip : '.x-monitor-tooltip',
|
||||
addButton : '.x-add',
|
||||
addAlbumButton : '.x-add-album',
|
||||
addSearchButton : '.x-add-search',
|
||||
addAlbumSearchButton : '.x-add-album-search',
|
||||
overview : '.x-overview'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-add' : '_addWithoutSearch',
|
||||
'click .x-add-album' : '_addWithoutSearch',
|
||||
'click .x-add-search' : '_addAndSearch',
|
||||
'click .x-add-album-search' : '_addAndSearch',
|
||||
'change .x-profile' : '_profileChanged',
|
||||
'change .x-root-folder' : '_rootFolderChanged',
|
||||
'change .x-album-folder' : '_albumFolderChanged',
|
||||
'change .x-artist-type' : '_artistTypeChanged',
|
||||
'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 defaultArtistType = Config.getValue(Config.Keys.DefaultSeriesType, 'standard');
|
||||
var defaultMonitorEpisodes = Config.getValue(Config.Keys.MonitorEpisodes, 'missing');
|
||||
|
||||
if (Profiles.get(defaultProfile)) {
|
||||
this.ui.profile.val(defaultProfile);
|
||||
}
|
||||
|
||||
if (RootFolders.get(defaultRoot)) {
|
||||
this.ui.rootFolder.val(defaultRoot);
|
||||
}
|
||||
|
||||
this.ui.albumFolder.prop('checked', useSeasonFolder);
|
||||
this.ui.artistType.val(defaultArtistType);
|
||||
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('AddArtist/MonitoringTooltipTemplate');
|
||||
var content = this.templateFunction();
|
||||
|
||||
this.ui.monitorTooltip.popover({
|
||||
content : content,
|
||||
html : true,
|
||||
trigger : 'hover',
|
||||
title : 'Track Monitoring Options',
|
||||
placement : 'right',
|
||||
container : this.$el
|
||||
});
|
||||
},
|
||||
|
||||
_configureTemplateHelpers : function() {
|
||||
var existingArtist = ArtistCollection.where({ SpotifyId : this.model.get('spotifyId') });
|
||||
|
||||
if (existingArtist.length > 0) {
|
||||
this.templateHelpers.existing = existingArtist[0].toJSON();
|
||||
}
|
||||
|
||||
this.templateHelpers.profiles = Profiles.toJSON();
|
||||
|
||||
if (!this.model.get('isExisting')) {
|
||||
this.templateHelpers.rootFolders = RootFolders.toJSON();
|
||||
}
|
||||
},
|
||||
|
||||
_onConfigUpdated : function(options) {
|
||||
if (options.key === Config.Keys.DefaultProfileId) {
|
||||
this.ui.profile.val(options.value);
|
||||
}
|
||||
|
||||
else if (options.key === Config.Keys.DefaultRootFolderId) {
|
||||
this.ui.rootFolder.val(options.value);
|
||||
}
|
||||
|
||||
else if (options.key === Config.Keys.UseAlbumFolder) {
|
||||
this.ui.seasonFolder.prop('checked', options.value);
|
||||
}
|
||||
|
||||
else if (options.key === Config.Keys.DefaultArtistType) {
|
||||
this.ui.artistType.val(options.value);
|
||||
}
|
||||
|
||||
else if (options.key === Config.Keys.MonitorEpisodes) {
|
||||
this.ui.monitor.val(options.value);
|
||||
}
|
||||
},
|
||||
|
||||
_profileChanged : function() {
|
||||
Config.setValue(Config.Keys.DefaultProfileId, this.ui.profile.val());
|
||||
},
|
||||
|
||||
_albumFolderChanged : function() {
|
||||
Config.setValue(Config.Keys.UseAlbumFolder, this.ui.albumFolder.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);
|
||||
}
|
||||
},
|
||||
|
||||
_artistTypeChanged : function() {
|
||||
Config.setValue(Config.Keys.DefaultArtistType, this.ui.artistType.val());
|
||||
},
|
||||
|
||||
_monitorChanged : function() {
|
||||
Config.setValue(Config.Keys.MonitorEpisodes, this.ui.monitor.val());
|
||||
},
|
||||
|
||||
_setRootFolder : function(options) {
|
||||
vent.trigger(vent.Commands.CloseModalCommand);
|
||||
this.ui.rootFolder.val(options.model.id);
|
||||
this._rootFolderChanged();
|
||||
},
|
||||
|
||||
_addWithoutSearch : function(evt) {
|
||||
console.log(evt);
|
||||
this._addArtist(false);
|
||||
},
|
||||
|
||||
_addAndSearch : function() {
|
||||
this._addArtist(true);
|
||||
},
|
||||
|
||||
_addArtist : function(searchForMissing) {
|
||||
// TODO: Refactor to handle multiple add buttons/albums
|
||||
var addButton = this.ui.addButton;
|
||||
var addSearchButton = this.ui.addSearchButton;
|
||||
console.log('_addArtist, searchForMissing=', searchForMissing);
|
||||
|
||||
addButton.addClass('disabled');
|
||||
addSearchButton.addClass('disabled');
|
||||
|
||||
var profile = this.ui.profile.val();
|
||||
var rootFolderPath = this.ui.rootFolder.children(':selected').text();
|
||||
var artistType = this.ui.artistType.val(); // Perhaps make this a differnitator between artist or Album?
|
||||
var albumFolder = this.ui.albumFolder.prop('checked');
|
||||
|
||||
var options = this._getAddArtistOptions();
|
||||
options.searchForMissing = searchForMissing;
|
||||
|
||||
this.model.set({
|
||||
profileId : profile,
|
||||
rootFolderPath : rootFolderPath,
|
||||
albumFolder : albumFolder,
|
||||
artistType : artistType,
|
||||
addOptions : options,
|
||||
monitored : true
|
||||
}, { silent : true });
|
||||
|
||||
var self = this;
|
||||
var promise = this.model.save();
|
||||
|
||||
if (searchForMissing) {
|
||||
this.ui.addSearchButton.spinForPromise(promise);
|
||||
}
|
||||
|
||||
else {
|
||||
this.ui.addButton.spinForPromise(promise);
|
||||
}
|
||||
|
||||
promise.always(function() {
|
||||
addButton.removeClass('disabled');
|
||||
addSearchButton.removeClass('disabled');
|
||||
});
|
||||
|
||||
promise.done(function() {
|
||||
console.log('[SearchResultView] _addArtist promise resolve:', self.model);
|
||||
ArtistCollection.add(self.model);
|
||||
|
||||
self.close();
|
||||
|
||||
Messenger.show({
|
||||
message : 'Added: ' + self.model.get('artistName'),
|
||||
actions : {
|
||||
goToArtist : {
|
||||
label : 'Go to Artist',
|
||||
action : function() {
|
||||
Backbone.history.navigate('/artist/' + self.model.get('artistSlug'), { trigger : true });
|
||||
}
|
||||
}
|
||||
},
|
||||
hideAfter : 8,
|
||||
hideOnNavigate : true
|
||||
});
|
||||
|
||||
vent.trigger(vent.Events.ArtistAdded, { artist : self.model });
|
||||
});
|
||||
},
|
||||
|
||||
_rootFoldersUpdated : function() {
|
||||
this._configureTemplateHelpers();
|
||||
this.render();
|
||||
},
|
||||
|
||||
_getAddArtistOptions : function() {
|
||||
var monitor = this.ui.monitor.val();
|
||||
//[TODO]: Refactor for albums
|
||||
var lastSeason = _.max(this.model.get('seasons'), 'seasonNumber');
|
||||
var firstSeason = _.min(_.reject(this.model.get('seasons'), { seasonNumber : 0 }), 'seasonNumber');
|
||||
|
||||
//this.model.setSeasonPass(firstSeason.seasonNumber); // TODO
|
||||
|
||||
var options = {
|
||||
ignoreTracksWithFiles : false,
|
||||
ignoreTracksWithoutFiles : false
|
||||
};
|
||||
|
||||
if (monitor === 'all') {
|
||||
return options;
|
||||
}
|
||||
|
||||
else if (monitor === 'future') {
|
||||
options.ignoreTracksWithFiles = true;
|
||||
options.ignoreTracksWithoutFiles = 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.ignoreTracksWithFiles = true;
|
||||
}
|
||||
|
||||
else if (monitor === 'existing') {
|
||||
options.ignoreTracksWithoutFiles = true;
|
||||
}
|
||||
|
||||
/*else if (monitor === 'none') {
|
||||
this.model.setSeasonPass(lastSeason.seasonNumber + 1);
|
||||
}*/
|
||||
|
||||
return options;
|
||||
}
|
||||
});
|
||||
|
||||
AsValidatedView.apply(view);
|
||||
|
||||
module.exports = view;
|
@ -0,0 +1,146 @@
|
||||
<div class="search-item {{#unless isExisting}}search-item-new{{/unless}}">
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h2 class="artist-title">
|
||||
<!--{{titleWithYear}}-->
|
||||
{{artistName}}
|
||||
|
||||
<!--<span class="labels">
|
||||
<span class="label label-default">{{network}}</span>
|
||||
{{#unless_eq status compare="continuing"}}
|
||||
<span class="label label-danger">Ended</span>
|
||||
{{/unless_eq}}
|
||||
</span>-->
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="row new-artist-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-lidarr-form-info monitor-tooltip x-monitor-tooltip"></i></label>
|
||||
<select class="form-control col-md-2 x-monitor">
|
||||
<option value="all">All</option>
|
||||
<option value="future">Future</option>
|
||||
<option value="missing">Missing</option>
|
||||
<option value="existing">Existing</option>
|
||||
<option value="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">
|
||||
|
||||
</div>-->
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label>Album Folders</label>
|
||||
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" class="x-season-folder"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
<div class="row">
|
||||
{{#unless existing}}
|
||||
{{#if artistName}}
|
||||
<div class="form-group col-md-2 col-md-offset-10">
|
||||
<!--Uncomment if we need to add even more controls to add artist-->
|
||||
<!--<label style="visibility: hidden">Add</label>-->
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-success add x-add" title="Add" data-artist="{{artistName}}">
|
||||
<i class="icon-lidarr-add"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-success add x-add-search" title="Add and Search for missing tracks" data-artist="{{artistName}}">
|
||||
<i class="icon-lidarr-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="col-md-2 col-md-offset-10">
|
||||
<button class="btn add-artist disabled">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<div class="col-md-2 col-md-offset-10">
|
||||
<a class="btn btn-default" href="{{route}}">
|
||||
Already Exists
|
||||
</a>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{{#each albums}}
|
||||
<div class="col-md-12" style="border:1px dashed black;">
|
||||
<div class="col-md-2">
|
||||
<a href="{{artworkUrl}}" target="_blank">
|
||||
<!-- {{poster}} -->
|
||||
<img class="album-poster" src="{{artworkUrl}}">
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<h2>{{albumName}} ({{year}})</h2>
|
||||
{{#unless existing}}
|
||||
{{#if albumName}}
|
||||
<div class="form-group col-md-offset-10">
|
||||
<!--Uncomment if we need to add even more controls to add artist-->
|
||||
<!--<label style="visibility: hidden">Add</label>-->
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-success add x-add-album" title="Add" data-album="{{albumName}}">
|
||||
<i class="icon-lidarr-add"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-success add x-add-album-search" title="Add and Search for missing tracks" data-album="{{albumName}}">
|
||||
<i class="icon-lidarr-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="col-md-2 col-md-offset-10">
|
||||
<button class="btn add-artist disabled">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<div class="col-md-2 col-md-offset-10">
|
||||
<a class="btn btn-default" href="{{route}}">
|
||||
Already Exists
|
||||
</a>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,13 @@
|
||||
<select class="form-control col-md-2 starting-album x-starting-album">
|
||||
|
||||
|
||||
{{#each this}}
|
||||
{{#if_eq seasonNumber compare="0"}}
|
||||
<option value="{{seasonNumber}}">Specials</option>
|
||||
{{else}}
|
||||
<option value="{{seasonNumber}}">Album {{seasonNumber}}</option>
|
||||
{{/if_eq}}
|
||||
{{/each}}
|
||||
|
||||
<option value="5000000">None</option>
|
||||
</select>
|
@ -0,0 +1,181 @@
|
||||
@import "../Shared/Styles/card.less";
|
||||
@import "../Shared/Styles/clickable.less";
|
||||
|
||||
#add-artist-screen {
|
||||
.existing-artist {
|
||||
|
||||
.card();
|
||||
margin : 30px 0px;
|
||||
|
||||
.unmapped-folder-path {
|
||||
padding: 20px;
|
||||
margin-left : 0px;
|
||||
font-weight : 100;
|
||||
font-size : 25px;
|
||||
text-align : center;
|
||||
}
|
||||
|
||||
.new-artist-loadmore {
|
||||
font-size : 30px;
|
||||
font-weight : 300;
|
||||
padding-top : 10px;
|
||||
padding-bottom : 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.new-artist {
|
||||
.search-item {
|
||||
.card();
|
||||
margin : 40px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.add-artist-search {
|
||||
margin-top : 20px;
|
||||
margin-bottom : 20px;
|
||||
}
|
||||
|
||||
.search-item {
|
||||
|
||||
padding-bottom : 20px;
|
||||
|
||||
.artist-title {
|
||||
margin-top : 5px;
|
||||
|
||||
.labels {
|
||||
margin-left : 10px;
|
||||
|
||||
.label {
|
||||
font-size : 12px;
|
||||
vertical-align : middle;
|
||||
}
|
||||
}
|
||||
|
||||
.year {
|
||||
font-style : italic;
|
||||
color : #aaaaaa;
|
||||
}
|
||||
}
|
||||
|
||||
.new-artist-overview {
|
||||
overflow : hidden;
|
||||
height : 103px;
|
||||
|
||||
.overview-internal {
|
||||
overflow : hidden;
|
||||
height : 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.artist-poster {
|
||||
min-width : 138px;
|
||||
min-height : 203px;
|
||||
max-width : 138px;
|
||||
max-height : 203px;
|
||||
margin : 10px;
|
||||
}
|
||||
|
||||
.album-poster {
|
||||
min-width : 100px;
|
||||
min-height : 100px;
|
||||
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,10 @@
|
||||
var Backbone = require('backbone');
|
||||
var AlbumModel = require('./AlbumModel');
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
model : AlbumModel,
|
||||
|
||||
comparator : function(season) {
|
||||
return -season.get('seasonNumber');
|
||||
}
|
||||
});
|
@ -0,0 +1,11 @@
|
||||
var Backbone = require('backbone');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults : {
|
||||
seasonNumber : 0
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
this.set('id', this.get('seasonNumber'));
|
||||
}
|
||||
});
|
@ -0,0 +1,50 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>Delete {{title}}</h3>
|
||||
</div>
|
||||
<div class="modal-body delete-artist-modal">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-3 hidden-xs">
|
||||
{{poster}}
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<div class="form-horizontal">
|
||||
<h3 class="path">{{path}}</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Delete all files</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" class="x-delete-files"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn slide-button btn-danger"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-lidarr-form-info" title="Do you want to delete all files from disk?"/>
|
||||
<i class="icon-lidarr-form-warning" title="This option is irreversible, use with extreme caution"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-offset-1 col-md-5 delete-files-info x-delete-files-info">
|
||||
{{episodeFileCount}} track files will be deleted
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="indicator x-indicator"><i class="icon-lidarr-spinner fa-spin"></i></span>
|
||||
<button class="btn" data-dismiss="modal">Cancel</button>
|
||||
<button class="btn btn-danger x-confirm-delete">Delete</button>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,41 @@
|
||||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.ItemView.extend({
|
||||
template : 'Series/Delete/DeleteSeriesTemplate',
|
||||
|
||||
events : {
|
||||
'click .x-confirm-delete' : 'removeSeries',
|
||||
'change .x-delete-files' : 'changeDeletedFiles'
|
||||
},
|
||||
|
||||
ui : {
|
||||
deleteFiles : '.x-delete-files',
|
||||
deleteFilesInfo : '.x-delete-files-info',
|
||||
indicator : '.x-indicator'
|
||||
},
|
||||
|
||||
removeSeries : function() {
|
||||
var self = this;
|
||||
var deleteFiles = this.ui.deleteFiles.prop('checked');
|
||||
this.ui.indicator.show();
|
||||
|
||||
this.model.destroy({
|
||||
data : { 'deleteFiles' : deleteFiles },
|
||||
wait : true
|
||||
}).done(function() {
|
||||
vent.trigger(vent.Events.SeriesDeleted, { series : self.model });
|
||||
vent.trigger(vent.Commands.CloseModalCommand);
|
||||
});
|
||||
},
|
||||
|
||||
changeDeletedFiles : function() {
|
||||
var deleteFiles = this.ui.deleteFiles.prop('checked');
|
||||
|
||||
if (deleteFiles) {
|
||||
this.ui.deleteFilesInfo.show();
|
||||
} else {
|
||||
this.ui.deleteFilesInfo.hide();
|
||||
}
|
||||
}
|
||||
});
|
@ -0,0 +1,44 @@
|
||||
var _ = require('underscore');
|
||||
var Marionette = require('marionette');
|
||||
var SeasonLayout = require('./AlbumLayout');
|
||||
var AsSortedCollectionView = require('../../Mixins/AsSortedCollectionView');
|
||||
|
||||
var view = Marionette.CollectionView.extend({
|
||||
|
||||
itemView : SeasonLayout,
|
||||
|
||||
initialize : function(options) {
|
||||
if (!options.trackCollection) {
|
||||
throw 'trackCollection is needed';
|
||||
}
|
||||
|
||||
this.trackCollection = options.trackCollection;
|
||||
this.artist = options.artist;
|
||||
},
|
||||
|
||||
itemViewOptions : function() {
|
||||
return {
|
||||
trackCollection : this.trackCollection,
|
||||
artist : this.artist
|
||||
};
|
||||
},
|
||||
|
||||
onTrackGrabbed : function(message) {
|
||||
if (message.track.artist.id !== this.trackCollection.artistId) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
_.each(message.track.tracks, function(track) {
|
||||
var ep = self.TrackCollection.get(track.id);
|
||||
ep.set('downloading', true);
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
|
||||
AsSortedCollectionView.call(view);
|
||||
|
||||
module.exports = view;
|
@ -0,0 +1,301 @@
|
||||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
var Backgrid = require('backgrid');
|
||||
var ToggleCell = require('../../Cells/EpisodeMonitoredCell');
|
||||
var EpisodeTitleCell = require('../../Cells/EpisodeTitleCell');
|
||||
var RelativeDateCell = require('../../Cells/RelativeDateCell');
|
||||
var EpisodeStatusCell = require('../../Cells/EpisodeStatusCell');
|
||||
var EpisodeActionsCell = require('../../Cells/EpisodeActionsCell');
|
||||
var TrackNumberCell = require('./TrackNumberCell');
|
||||
var TrackWarningCell = require('./TrackWarningCell');
|
||||
var CommandController = require('../../Commands/CommandController');
|
||||
var EpisodeFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEditorLayout');
|
||||
var moment = require('moment');
|
||||
var _ = require('underscore');
|
||||
var Messenger = require('../../Shared/Messenger');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'Artist/Details/ArtistLayoutTemplate',
|
||||
|
||||
ui : {
|
||||
seasonSearch : '.x-album-search',
|
||||
seasonMonitored : '.x-album-monitored',
|
||||
seasonRename : '.x-album-rename'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-album-episode-file-editor' : '_openEpisodeFileEditor',
|
||||
'click .x-album-monitored' : '_albumMonitored',
|
||||
'click .x-album-search' : '_albumSearch',
|
||||
'click .x-album-rename' : '_albumRename',
|
||||
'click .x-show-hide-episodes' : '_showHideEpisodes',
|
||||
'dblclick .artist-album h2' : '_showHideEpisodes'
|
||||
},
|
||||
|
||||
regions : {
|
||||
episodeGrid : '.x-episode-grid'
|
||||
},
|
||||
|
||||
columns : [
|
||||
{
|
||||
name : 'monitored',
|
||||
label : '',
|
||||
cell : ToggleCell,
|
||||
trueClass : 'icon-lidarr-monitored',
|
||||
falseClass : 'icon-lidarr-unmonitored',
|
||||
tooltip : 'Toggle monitored status',
|
||||
sortable : false
|
||||
},
|
||||
{
|
||||
name : 'trackNumber',
|
||||
label : '#',
|
||||
cell : TrackNumberCell
|
||||
},
|
||||
{
|
||||
name : 'this',
|
||||
label : '',
|
||||
cell : TrackWarningCell,
|
||||
sortable : false,
|
||||
className : 'track-warning-cell'
|
||||
},
|
||||
{
|
||||
name : 'this',
|
||||
label : 'Title',
|
||||
hideSeriesLink : true,
|
||||
cell : EpisodeTitleCell,
|
||||
sortable : false
|
||||
},
|
||||
{
|
||||
name : 'airDateUtc',
|
||||
label : 'Air Date',
|
||||
cell : RelativeDateCell
|
||||
},
|
||||
{
|
||||
name : 'status',
|
||||
label : 'Status',
|
||||
cell : EpisodeStatusCell,
|
||||
sortable : false
|
||||
},
|
||||
{
|
||||
name : 'this',
|
||||
label : '',
|
||||
cell : EpisodeActionsCell,
|
||||
sortable : false
|
||||
}
|
||||
],
|
||||
|
||||
templateHelpers : function() {
|
||||
var episodeCount = this.episodeCollection.filter(function(episode) {
|
||||
return episode.get('hasFile') || episode.get('monitored') && moment(episode.get('airDateUtc')).isBefore(moment());
|
||||
}).length;
|
||||
|
||||
var episodeFileCount = this.episodeCollection.where({ hasFile : true }).length;
|
||||
var percentOfEpisodes = 100;
|
||||
|
||||
if (episodeCount > 0) {
|
||||
percentOfEpisodes = episodeFileCount / episodeCount * 100;
|
||||
}
|
||||
|
||||
return {
|
||||
showingEpisodes : this.showingEpisodes,
|
||||
episodeCount : episodeCount,
|
||||
episodeFileCount : episodeFileCount,
|
||||
percentOfEpisodes : percentOfEpisodes
|
||||
};
|
||||
},
|
||||
|
||||
initialize : function(options) {
|
||||
if (!options.episodeCollection) {
|
||||
throw 'episodeCollection is required';
|
||||
}
|
||||
|
||||
this.series = options.series;
|
||||
this.fullEpisodeCollection = options.episodeCollection;
|
||||
this.episodeCollection = this.fullEpisodeCollection.bySeason(this.model.get('seasonNumber'));
|
||||
this._updateEpisodeCollection();
|
||||
|
||||
this.showingEpisodes = this._shouldShowEpisodes();
|
||||
|
||||
this.listenTo(this.model, 'sync', this._afterSeasonMonitored);
|
||||
this.listenTo(this.episodeCollection, 'sync', this.render);
|
||||
|
||||
this.listenTo(this.fullEpisodeCollection, 'sync', this._refreshEpisodes);
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
if (this.showingEpisodes) {
|
||||
this._showEpisodes();
|
||||
}
|
||||
|
||||
this._setSeasonMonitoredState();
|
||||
|
||||
CommandController.bindToCommand({
|
||||
element : this.ui.seasonSearch,
|
||||
command : {
|
||||
name : 'seasonSearch',
|
||||
seriesId : this.series.id,
|
||||
seasonNumber : this.model.get('seasonNumber')
|
||||
}
|
||||
});
|
||||
|
||||
CommandController.bindToCommand({
|
||||
element : this.ui.seasonRename,
|
||||
command : {
|
||||
name : 'renameFiles',
|
||||
seriesId : this.series.id,
|
||||
seasonNumber : this.model.get('seasonNumber')
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_seasonSearch : function() {
|
||||
CommandController.Execute('seasonSearch', {
|
||||
name : 'seasonSearch',
|
||||
seriesId : this.series.id,
|
||||
seasonNumber : this.model.get('seasonNumber')
|
||||
});
|
||||
},
|
||||
|
||||
_seasonRename : function() {
|
||||
vent.trigger(vent.Commands.ShowRenamePreview, {
|
||||
series : this.series,
|
||||
seasonNumber : this.model.get('seasonNumber')
|
||||
});
|
||||
},
|
||||
|
||||
_seasonMonitored : function() {
|
||||
if (!this.series.get('monitored')) {
|
||||
|
||||
Messenger.show({
|
||||
message : 'Unable to change monitored state when series is not monitored',
|
||||
type : 'error'
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var name = 'monitored';
|
||||
this.model.set(name, !this.model.get(name));
|
||||
this.series.setSeasonMonitored(this.model.get('seasonNumber'));
|
||||
|
||||
var savePromise = this.series.save().always(this._afterSeasonMonitored.bind(this));
|
||||
|
||||
this.ui.seasonMonitored.spinForPromise(savePromise);
|
||||
},
|
||||
|
||||
_afterSeasonMonitored : function() {
|
||||
var self = this;
|
||||
|
||||
_.each(this.episodeCollection.models, function(episode) {
|
||||
episode.set({ monitored : self.model.get('monitored') });
|
||||
});
|
||||
|
||||
this.render();
|
||||
},
|
||||
|
||||
_setSeasonMonitoredState : function() {
|
||||
this.ui.seasonMonitored.removeClass('icon-lidarr-spinner fa-spin');
|
||||
|
||||
if (this.model.get('monitored')) {
|
||||
this.ui.seasonMonitored.addClass('icon-lidarr-monitored');
|
||||
this.ui.seasonMonitored.removeClass('icon-lidarr-unmonitored');
|
||||
} else {
|
||||
this.ui.seasonMonitored.addClass('icon-lidarr-unmonitored');
|
||||
this.ui.seasonMonitored.removeClass('icon-lidarr-monitored');
|
||||
}
|
||||
},
|
||||
|
||||
_showEpisodes : function() {
|
||||
this.episodeGrid.show(new Backgrid.Grid({
|
||||
columns : this.columns,
|
||||
collection : this.episodeCollection,
|
||||
className : 'table table-hover season-grid'
|
||||
}));
|
||||
},
|
||||
|
||||
_shouldShowEpisodes : function() {
|
||||
var startDate = moment().add('month', -1);
|
||||
var endDate = moment().add('year', 1);
|
||||
|
||||
return this.episodeCollection.some(function(episode) {
|
||||
var airDate = episode.get('airDateUtc');
|
||||
|
||||
if (airDate) {
|
||||
var airDateMoment = moment(airDate);
|
||||
|
||||
if (airDateMoment.isAfter(startDate) && airDateMoment.isBefore(endDate)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
},
|
||||
|
||||
_showHideEpisodes : function() {
|
||||
if (this.showingEpisodes) {
|
||||
this.showingEpisodes = false;
|
||||
this.episodeGrid.close();
|
||||
} else {
|
||||
this.showingEpisodes = true;
|
||||
this._showEpisodes();
|
||||
}
|
||||
|
||||
this.templateHelpers.showingEpisodes = this.showingEpisodes;
|
||||
this.render();
|
||||
},
|
||||
|
||||
_episodeMonitoredToggled : function(options) {
|
||||
var model = options.model;
|
||||
var shiftKey = options.shiftKey;
|
||||
|
||||
if (!this.episodeCollection.get(model.get('id'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
var lastToggled = this.episodeCollection.lastToggled;
|
||||
|
||||
if (!lastToggled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentIndex = this.episodeCollection.indexOf(model);
|
||||
var lastIndex = this.episodeCollection.indexOf(lastToggled);
|
||||
|
||||
var low = Math.min(currentIndex, lastIndex);
|
||||
var high = Math.max(currentIndex, lastIndex);
|
||||
var range = _.range(low + 1, high);
|
||||
|
||||
this.episodeCollection.lastToggled = model;
|
||||
},
|
||||
|
||||
_updateEpisodeCollection : function() {
|
||||
var self = this;
|
||||
|
||||
this.episodeCollection.add(this.fullEpisodeCollection.bySeason(this.model.get('seasonNumber')).models, { merge : true });
|
||||
|
||||
this.episodeCollection.each(function(model) {
|
||||
model.episodeCollection = self.episodeCollection;
|
||||
});
|
||||
},
|
||||
|
||||
_refreshEpisodes : function() {
|
||||
this._updateEpisodeCollection();
|
||||
this.episodeCollection.fullCollection.sort();
|
||||
this.render();
|
||||
},
|
||||
|
||||
_openEpisodeFileEditor : function() {
|
||||
var view = new EpisodeFileEditorLayout({
|
||||
model : this.model,
|
||||
series : this.series,
|
||||
episodeCollection : this.episodeCollection
|
||||
});
|
||||
|
||||
vent.trigger(vent.Commands.OpenModalCommand, view);
|
||||
}
|
||||
});
|
@ -0,0 +1,50 @@
|
||||
<div class="artist-album" id="season-{{seasonNumber}}">
|
||||
<h2>
|
||||
<i class="x-album-monitored album-monitored clickable" title="Toggle album monitored status"/>
|
||||
|
||||
{{#if seasonNumber}}
|
||||
Season {{seasonNumber}}
|
||||
{{else}}
|
||||
Specials
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if_eq episodeCount compare=0}}
|
||||
{{#if monitored}}
|
||||
<span class="badge badge-primary album-status" title="No aired episodes"> </span>
|
||||
{{else}}
|
||||
<span class="badge badge-warning album-status" title="Album is not monitored"> </span>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#if_eq percentOfEpisodes compare=100}}
|
||||
<span class="badge badge-success album-status" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded">{{episodeFileCount}} / {{episodeCount}}</span>
|
||||
{{else}}
|
||||
<span class="badge badge-danger album-status" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded">{{episodeFileCount}} / {{episodeCount}}</span>
|
||||
{{/if_eq}}
|
||||
{{/if_eq}}
|
||||
|
||||
<span class="album-actions pull-right">
|
||||
<div class="x-album-episode-file-editor">
|
||||
<i class="icon-lidarr-episode-file" title="Modify episode files for album"/>
|
||||
</div>
|
||||
<div class="x-album-rename">
|
||||
<i class="icon-lidarr-rename" title="Preview rename for album {{seasonNumber}}"/>
|
||||
</div>
|
||||
<div class="x-album-search">
|
||||
<i class="icon-lidarr-search" title="Search for monitored episodes in album {{seasonNumber}}"/>
|
||||
</div>
|
||||
</span>
|
||||
</h2>
|
||||
<div class="show-hide-episodes x-show-hide-episodes">
|
||||
<h4>
|
||||
{{#if showingEpisodes}}
|
||||
<i class="icon-lidarr-panel-hide"/>
|
||||
Hide Episodes
|
||||
{{else}}
|
||||
<i class="icon-lidarr-panel-show"/>
|
||||
Show Episodes
|
||||
{{/if}}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="x-episode-grid table-responsive"></div>
|
||||
</div>
|
@ -0,0 +1,259 @@
|
||||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
var vent = require('vent');
|
||||
var reqres = require('../../reqres');
|
||||
var Marionette = require('marionette');
|
||||
var Backbone = require('backbone');
|
||||
var ArtistCollection = require('../ArtistCollection');
|
||||
var TrackCollection = require('../TrackCollection');
|
||||
var TrackFileCollection = require('../TrackFileCollection');
|
||||
var AlbumCollection = require('../AlbumCollection');
|
||||
var AlbumCollectionView = require('./AlbumCollectionView');
|
||||
var InfoView = require('./InfoView');
|
||||
var CommandController = require('../../Commands/CommandController');
|
||||
var LoadingView = require('../../Shared/LoadingView');
|
||||
var TrackFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEditorLayout');
|
||||
require('backstrech');
|
||||
require('../../Mixins/backbone.signalr.mixin');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
itemViewContainer : '.x-artist-albums',
|
||||
template : 'Artist/Details/ArtistDetailsTemplate',
|
||||
|
||||
regions : {
|
||||
albums : '#albums',
|
||||
info : '#info'
|
||||
},
|
||||
|
||||
ui : {
|
||||
header : '.x-header',
|
||||
monitored : '.x-monitored',
|
||||
edit : '.x-edit',
|
||||
refresh : '.x-refresh',
|
||||
rename : '.x-rename',
|
||||
search : '.x-search',
|
||||
poster : '.x-artist-poster'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-episode-file-editor' : '_openEpisodeFileEditor',
|
||||
'click .x-monitored' : '_toggleMonitored',
|
||||
'click .x-edit' : '_editArtist',
|
||||
'click .x-refresh' : '_refreshArtist',
|
||||
'click .x-rename' : '_renameArtist',
|
||||
'click .x-search' : '_artistSearch'
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
this.artistCollection = ArtistCollection.clone();
|
||||
this.artistCollection.shadowCollection.bindSignalR();
|
||||
|
||||
this.listenTo(this.model, 'change:monitored', this._setMonitoredState);
|
||||
this.listenTo(this.model, 'remove', this._artistRemoved);
|
||||
this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete);
|
||||
|
||||
this.listenTo(this.model, 'change', function(model, options) {
|
||||
if (options && options.changeSource === 'signalr') {
|
||||
this._refresh();
|
||||
}
|
||||
});
|
||||
|
||||
this.listenTo(this.model, 'change:images', this._updateImages);
|
||||
},
|
||||
|
||||
onShow : function() {
|
||||
this._showBackdrop();
|
||||
this._showSeasons();
|
||||
this._setMonitoredState();
|
||||
this._showInfo();
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
CommandController.bindToCommand({
|
||||
element : this.ui.refresh,
|
||||
command : {
|
||||
name : 'refreshArtist'
|
||||
}
|
||||
});
|
||||
CommandController.bindToCommand({
|
||||
element : this.ui.search,
|
||||
command : {
|
||||
name : 'artistSearch'
|
||||
}
|
||||
});
|
||||
|
||||
CommandController.bindToCommand({
|
||||
element : this.ui.rename,
|
||||
command : {
|
||||
name : 'renameFiles',
|
||||
seriesId : this.model.id,
|
||||
seasonNumber : -1
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onClose : function() {
|
||||
if (this._backstrech) {
|
||||
this._backstrech.destroy();
|
||||
delete this._backstrech;
|
||||
}
|
||||
|
||||
$('body').removeClass('backdrop');
|
||||
reqres.removeHandler(reqres.Requests.GetEpisodeFileById);
|
||||
},
|
||||
|
||||
_getImage : function(type) {
|
||||
var image = _.where(this.model.get('images'), { coverType : type });
|
||||
|
||||
if (image && image[0]) {
|
||||
return image[0].url;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
_toggleMonitored : function() {
|
||||
var savePromise = this.model.save('monitored', !this.model.get('monitored'), { wait : true });
|
||||
|
||||
this.ui.monitored.spinForPromise(savePromise);
|
||||
},
|
||||
|
||||
_setMonitoredState : function() {
|
||||
var monitored = this.model.get('monitored');
|
||||
|
||||
this.ui.monitored.removeAttr('data-idle-icon');
|
||||
this.ui.monitored.removeClass('fa-spin icon-lidarr-spinner');
|
||||
|
||||
if (monitored) {
|
||||
this.ui.monitored.addClass('icon-lidarr-monitored');
|
||||
this.ui.monitored.removeClass('icon-lidarr-unmonitored');
|
||||
this.$el.removeClass('series-not-monitored');
|
||||
} else {
|
||||
this.ui.monitored.addClass('icon-lidarr-unmonitored');
|
||||
this.ui.monitored.removeClass('icon-lidarr-monitored');
|
||||
this.$el.addClass('series-not-monitored');
|
||||
}
|
||||
},
|
||||
|
||||
_editArtist : function() {
|
||||
vent.trigger(vent.Commands.EditArtistCommand, { artist : this.model });
|
||||
},
|
||||
|
||||
_refreshArtist : function() {
|
||||
CommandController.Execute('refreshArtist', {
|
||||
name : 'refreshArtist',
|
||||
seriesId : this.model.id
|
||||
});
|
||||
},
|
||||
|
||||
_artistRemoved : function() {
|
||||
Backbone.history.navigate('/', { trigger : true });
|
||||
},
|
||||
|
||||
_renameArtist : function() {
|
||||
vent.trigger(vent.Commands.ShowRenamePreview, { artist : this.model });
|
||||
},
|
||||
|
||||
_artistSearch : function() {
|
||||
console.log('_artistSearch:', this.model);
|
||||
CommandController.Execute('artistSearch', {
|
||||
name : 'artistSearch',
|
||||
artistId : this.model.id
|
||||
});
|
||||
},
|
||||
|
||||
_showAlbums : function() {
|
||||
var self = this;
|
||||
|
||||
this.albums.show(new LoadingView());
|
||||
|
||||
this.albumCollection = new AlbumCollection(this.model.get('albums'));
|
||||
this.trackCollection = new TrackCollection({ artistId : this.model.id }).bindSignalR();
|
||||
this.trackFileCollection = new TrackFileCollection({ artistId : this.model.id }).bindSignalR();
|
||||
|
||||
reqres.setHandler(reqres.Requests.GetEpisodeFileById, function(trackFileId) {
|
||||
return self.trackFileCollection.get(trackFileId);
|
||||
});
|
||||
|
||||
reqres.setHandler(reqres.Requests.GetAlternateNameBySeasonNumber, function(artistId, seasonNumber, sceneSeasonNumber) {
|
||||
if (self.model.get('id') !== artistId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (sceneSeasonNumber === undefined) {
|
||||
sceneSeasonNumber = seasonNumber;
|
||||
}
|
||||
|
||||
return _.where(self.model.get('alternateTitles'),
|
||||
function(alt) {
|
||||
return alt.sceneSeasonNumber === sceneSeasonNumber || alt.seasonNumber === seasonNumber;
|
||||
});
|
||||
});
|
||||
|
||||
$.when(this.trackCollection.fetch(), this.trackFileCollection.fetch()).done(function() {
|
||||
var albumCollectionView = new AlbumCollectionView({
|
||||
collection : self.albumCollection,
|
||||
trackCollection : self.trackCollection,
|
||||
artist : self.model
|
||||
});
|
||||
|
||||
if (!self.isClosed) {
|
||||
self.albums.show(albumCollectionView);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_showInfo : function() {
|
||||
this.info.show(new InfoView({
|
||||
model : this.model,
|
||||
trackFileCollection : this.trackFileCollection
|
||||
}));
|
||||
},
|
||||
|
||||
_commandComplete : function(options) {
|
||||
if (options.command.get('name') === 'renamefiles') {
|
||||
if (options.command.get('artistId') === this.model.get('id')) {
|
||||
this._refresh();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_refresh : function() {
|
||||
this.albumCollection.add(this.model.get('albums'), { merge : true });
|
||||
this.trackCollection.fetch();
|
||||
this.trackFileCollection.fetch();
|
||||
|
||||
this._setMonitoredState();
|
||||
this._showInfo();
|
||||
},
|
||||
|
||||
_openTrackFileEditor : function() {
|
||||
var view = new TrackFileEditorLayout({
|
||||
artist : this.model,
|
||||
trackCollection : this.trackCollection
|
||||
});
|
||||
|
||||
vent.trigger(vent.Commands.OpenModalCommand, view);
|
||||
},
|
||||
|
||||
_updateImages : function () {
|
||||
var poster = this._getImage('poster');
|
||||
|
||||
if (poster) {
|
||||
this.ui.poster.attr('src', poster);
|
||||
}
|
||||
|
||||
this._showBackdrop();
|
||||
},
|
||||
|
||||
_showBackdrop : function () {
|
||||
$('body').addClass('backdrop');
|
||||
var fanArt = this._getImage('fanart');
|
||||
|
||||
if (fanArt) {
|
||||
this._backstrech = $.backstretch(fanArt);
|
||||
} else {
|
||||
$('body').removeClass('backdrop');
|
||||
}
|
||||
}
|
||||
});
|
@ -0,0 +1,35 @@
|
||||
<div class="row series-page-header">
|
||||
<div class="visible-lg col-lg-2 poster">
|
||||
{{poster}}
|
||||
</div>
|
||||
<div class="col-md-12 col-lg-10">
|
||||
<div>
|
||||
<h1 class="header-text">
|
||||
<i class="x-monitored" title="Toggle monitored state for entire series"/>
|
||||
{{title}}
|
||||
<div class="series-actions pull-right">
|
||||
<div class="x-episode-file-editor">
|
||||
<i class="icon-lidarr-episode-file" title="Modify episode files for series"/>
|
||||
</div>
|
||||
<div class="x-refresh">
|
||||
<i class="icon-lidarr-refresh icon-can-spin" title="Update series info and scan disk"/>
|
||||
</div>
|
||||
<div class="x-rename">
|
||||
<i class="icon-lidarr-rename" title="Preview rename for all episodes"/>
|
||||
</div>
|
||||
<div class="x-search">
|
||||
<i class="icon-lidarr-search" title="Search for monitored episodes in this series"/>
|
||||
</div>
|
||||
<div class="x-edit">
|
||||
<i class="icon-lidarr-edit" title="Edit series"/>
|
||||
</div>
|
||||
</div>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="series-detail-overview">
|
||||
{{overview}}
|
||||
</div>
|
||||
<div id="info" class="series-info"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="seasons"></div>
|
@ -0,0 +1,18 @@
|
||||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.ItemView.extend({
|
||||
template : 'Series/Details/InfoViewTemplate',
|
||||
|
||||
initialize : function(options) {
|
||||
this.episodeFileCollection = options.episodeFileCollection;
|
||||
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
this.listenTo(this.episodeFileCollection, 'sync', this.render);
|
||||
},
|
||||
|
||||
templateHelpers : function() {
|
||||
return {
|
||||
fileCount : this.episodeFileCollection.length
|
||||
};
|
||||
}
|
||||
});
|
@ -0,0 +1,73 @@
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
{{profile profileId}}
|
||||
|
||||
{{#if network}}
|
||||
<span class="label label-info">{{network}}</span>
|
||||
{{/if}}
|
||||
|
||||
<span class="label label-info">{{runtime}} minutes</span>
|
||||
<span class="label label-info">{{path}}</span>
|
||||
|
||||
{{#if ratings}}
|
||||
<span class="label label-info" title="{{ratings.votes}} vote{{#if_gt ratings.votes compare="1"}}s{{/if_gt}}">{{ratings.value}}</span>
|
||||
{{/if}}
|
||||
|
||||
<span class="label label-info">{{Bytes sizeOnDisk}}</span>
|
||||
|
||||
{{#if_eq fileCount compare="1"}}
|
||||
<span class="label label-info"> 1 file</span>
|
||||
{{else}}
|
||||
<span class="label label-info"> {{fileCount}} files</span>
|
||||
{{/if_eq}}
|
||||
|
||||
{{#if_eq status compare="continuing"}}
|
||||
<span class="label label-info">Continuing</span>
|
||||
{{else}}
|
||||
<span class="label label-default">Ended</span>
|
||||
{{/if_eq}}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<span class="series-info-links">
|
||||
<a href="{{traktUrl}}" class="label label-info">Trakt</a>
|
||||
|
||||
<a href="{{tvdbUrl}}" class="label label-info">The TVDB</a>
|
||||
|
||||
{{#if imdbId}}
|
||||
<a href="{{imdbUrl}}" class="label label-info">IMDB</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if tvRageId}}
|
||||
<a href="{{tvRageUrl}}" class="label label-info">TV Rage</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if tvMazeId}}
|
||||
<a href="{{tvMazeUrl}}" class="label label-info">TV Maze</a>
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if alternateTitles}}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{{#each alternateTitles}}
|
||||
{{#if_eq seasonNumber compare="-1"}}
|
||||
<span class="label label-default">{{title}}</span>
|
||||
{{/if_eq}}
|
||||
|
||||
{{#if_eq sceneSeasonNumber compare="-1"}}
|
||||
<span class="label label-default">{{title}}</span>
|
||||
{{/if_eq}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if tags}}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{{tagDisplay tags}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
@ -0,0 +1,47 @@
|
||||
var Marionette = require('marionette');
|
||||
var NzbDroneCell = require('../../Cells/NzbDroneCell');
|
||||
var reqres = require('../../reqres');
|
||||
var ArtistCollection = require('../ArtistCollection');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'episode-number-cell',
|
||||
template : 'Artist/Details/TrackNumberCellTemplate',
|
||||
|
||||
render : function() {
|
||||
this.$el.empty();
|
||||
this.$el.html(this.model.get('trackNumber'));
|
||||
|
||||
var series = ArtistCollection.get(this.model.get('seriesId'));
|
||||
|
||||
if (series.get('seriesType') === 'anime' && this.model.has('absoluteEpisodeNumber')) {
|
||||
this.$el.html('{0} ({1})'.format(this.model.get('episodeNumber'), this.model.get('absoluteEpisodeNumber')));
|
||||
}
|
||||
|
||||
var alternateTitles = [];
|
||||
|
||||
if (reqres.hasHandler(reqres.Requests.GetAlternateNameBySeasonNumber)) {
|
||||
alternateTitles = reqres.request(reqres.Requests.GetAlternateNameBySeasonNumber, this.model.get('seriesId'), this.model.get('seasonNumber'), this.model.get('sceneSeasonNumber'));
|
||||
}
|
||||
|
||||
if (this.model.get('sceneSeasonNumber') > 0 || this.model.get('sceneEpisodeNumber') > 0 || this.model.has('sceneAbsoluteEpisodeNumber') || alternateTitles.length > 0) {
|
||||
this.templateFunction = Marionette.TemplateCache.get(this.template);
|
||||
|
||||
var json = this.model.toJSON();
|
||||
json.alternateTitles = alternateTitles;
|
||||
|
||||
var html = this.templateFunction(json);
|
||||
|
||||
this.$el.popover({
|
||||
content : html,
|
||||
html : true,
|
||||
trigger : 'hover',
|
||||
title : 'Scene Information',
|
||||
placement : 'right',
|
||||
container : this.$el
|
||||
});
|
||||
}
|
||||
|
||||
this.delegateEvents();
|
||||
return this;
|
||||
}
|
||||
});
|
@ -0,0 +1,39 @@
|
||||
<div class="scene-info">
|
||||
{{#if sceneSeasonNumber}}
|
||||
<div class="row">
|
||||
<div class="key">Season</div>
|
||||
<div class="value">{{sceneSeasonNumber}}</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if sceneEpisodeNumber}}
|
||||
<div class="row">
|
||||
<div class="key">Episode</div>
|
||||
<div class="value">{{sceneEpisodeNumber}}</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if sceneAbsoluteEpisodeNumber}}
|
||||
<div class="row">
|
||||
<div class="key">Absolute</div>
|
||||
<div class="value">{{sceneAbsoluteEpisodeNumber}}</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if alternateTitles}}
|
||||
<div class="row">
|
||||
{{#if_gt alternateTitles.length compare="1"}}
|
||||
<div class="key">Titles</div>
|
||||
{{else}}
|
||||
<div class="key">Title</div>
|
||||
{{/if_gt}}
|
||||
<div class="value">
|
||||
<ul>
|
||||
{{#each alternateTitles}}
|
||||
<li>{{title}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
@ -0,0 +1,21 @@
|
||||
var NzbDroneCell = require('../../Cells/NzbDroneCell');
|
||||
var ArtistCollection = require('../ArtistCollection');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'track-warning-cell',
|
||||
|
||||
render : function() {
|
||||
this.$el.empty();
|
||||
|
||||
if (this.model.get('unverifiedSceneNumbering')) {
|
||||
this.$el.html('<i class="icon-lidarr-form-warning" title="Scene number hasn\'t been verified yet."></i>');
|
||||
}
|
||||
|
||||
else if (ArtistCollection.get(this.model.get('artistId')).get('artistType') === 'anime' && this.model.get('seasonNumber') > 0 && !this.model.has('absoluteEpisodeNumber')) {
|
||||
this.$el.html('<i class="icon-lidarr-form-warning" title="Track does not have an absolute track number"></i>');
|
||||
}
|
||||
|
||||
this.delegateEvents();
|
||||
return this;
|
||||
}
|
||||
});
|
@ -0,0 +1,54 @@
|
||||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
var Profiles = require('../../Profile/ProfileCollection');
|
||||
var AsModelBoundView = require('../../Mixins/AsModelBoundView');
|
||||
var AsValidatedView = require('../../Mixins/AsValidatedView');
|
||||
var AsEditModalView = require('../../Mixins/AsEditModalView');
|
||||
require('../../Mixins/TagInput');
|
||||
require('../../Mixins/FileBrowser');
|
||||
|
||||
var view = Marionette.ItemView.extend({
|
||||
template : 'Artist/Edit/EditArtistViewTemplate',
|
||||
|
||||
ui : {
|
||||
profile : '.x-profile',
|
||||
path : '.x-path',
|
||||
tags : '.x-tags'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-remove' : '_removeArtist'
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
this.model.set('profiles', Profiles);
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
this.ui.path.fileBrowser();
|
||||
this.ui.tags.tagInput({
|
||||
model : this.model,
|
||||
property : 'tags'
|
||||
});
|
||||
},
|
||||
|
||||
_onBeforeSave : function() {
|
||||
var profileId = this.ui.profile.val();
|
||||
this.model.set({ profileId : profileId });
|
||||
},
|
||||
|
||||
_onAfterSave : function() {
|
||||
this.trigger('saved');
|
||||
vent.trigger(vent.Commands.CloseModalCommand);
|
||||
},
|
||||
|
||||
_removeSeries : function() {
|
||||
vent.trigger(vent.Commands.DeleteSeriesCommand, { series : this.model });
|
||||
}
|
||||
});
|
||||
|
||||
AsModelBoundView.call(view);
|
||||
AsValidatedView.call(view);
|
||||
AsEditModalView.call(view);
|
||||
|
||||
module.exports = view;
|
@ -0,0 +1,104 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>{{title}}</h3>
|
||||
</div>
|
||||
<div class="modal-body edit-artist-modal">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 hidden-xs">
|
||||
{{poster}}
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<div class="form-horizontal">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Monitored</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="monitored"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-lidarr-form-info" title="Should Lidarr download tracks for this artist?"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Use Album Folder</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" name="seasonFolder"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
|
||||
<span class="help-inline-checkbox">
|
||||
<i class="icon-lidarr-form-info" title="Should downloaded tracks be stored in album folders?"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Profile</label>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<select class="form-control x-profile" id="inputProfile" name="profileId">
|
||||
{{#each profiles.models}}
|
||||
<option value="{{id}}">{{attributes.name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Artist Type</label>
|
||||
<div class="col-sm-4">
|
||||
{{> ArtistTypeSelectionPartial}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Path</label>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<input type="text" class="form-control x-path" placeholder="Path" name="path">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-4 control-label">Tags</label>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<input type="text" class="form-control x-tags">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-danger pull-left x-remove">Delete</button>
|
||||
|
||||
<span class="indicator x-indicator"><i class="icon-lidarr-spinner fa-spin"></i></span>
|
||||
<button class="btn" data-dismiss="modal">Cancel</button>
|
||||
<button class="btn btn-primary x-save">Save</button>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,126 @@
|
||||
var _ = require('underscore');
|
||||
var Marionette = require('marionette');
|
||||
var vent = require('vent');
|
||||
var Profiles = require('../../Profile/ProfileCollection');
|
||||
var RootFolders = require('../../AddArtist/RootFolders/RootFolderCollection');
|
||||
var RootFolderLayout = require('../../AddArtist/RootFolders/RootFolderLayout');
|
||||
var UpdateFilesArtistView = require('./Organize/OrganizeFilesView');
|
||||
var Config = require('../../Config');
|
||||
|
||||
module.exports = Marionette.ItemView.extend({
|
||||
template : 'Artist/Editor/ArtistEditorFooterViewTemplate',
|
||||
|
||||
ui : {
|
||||
monitored : '.x-monitored',
|
||||
profile : '.x-profiles',
|
||||
albumFolder : '.x-album-folder',
|
||||
rootFolder : '.x-root-folder',
|
||||
selectedCount : '.x-selected-count',
|
||||
container : '.artist-editor-footer',
|
||||
actions : '.x-action'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-save' : '_updateAndSave',
|
||||
'change .x-root-folder' : '_rootFolderChanged',
|
||||
'click .x-organize-files' : '_organizeFiles'
|
||||
},
|
||||
|
||||
templateHelpers : function() {
|
||||
return {
|
||||
profiles : Profiles,
|
||||
rootFolders : RootFolders.toJSON()
|
||||
};
|
||||
},
|
||||
|
||||
initialize : function(options) {
|
||||
this.artistCollection = options.collection;
|
||||
|
||||
RootFolders.fetch().done(function() {
|
||||
RootFolders.synced = true;
|
||||
});
|
||||
|
||||
this.editorGrid = options.editorGrid;
|
||||
this.listenTo(this.artistCollection, 'backgrid:selected', this._updateInfo);
|
||||
this.listenTo(RootFolders, 'all', this.render);
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
this._updateInfo();
|
||||
},
|
||||
|
||||
_updateAndSave : function() {
|
||||
var selected = this.editorGrid.getSelectedModels();
|
||||
|
||||
var monitored = this.ui.monitored.val();
|
||||
var profile = this.ui.profile.val();
|
||||
var albumFolder = this.ui.albumFolder.val();
|
||||
var rootFolder = this.ui.rootFolder.val();
|
||||
|
||||
_.each(selected, function(model) {
|
||||
if (monitored === 'true') {
|
||||
model.set('monitored', true);
|
||||
} else if (monitored === 'false') {
|
||||
model.set('monitored', false);
|
||||
}
|
||||
|
||||
if (profile !== 'noChange') {
|
||||
model.set('profileId', parseInt(profile, 10));
|
||||
}
|
||||
|
||||
if (albumFolder === 'true') {
|
||||
model.set('albumFolder', true);
|
||||
} else if (albumFolder === 'false') {
|
||||
model.set('albumFolder', false);
|
||||
}
|
||||
|
||||
if (rootFolder !== 'noChange') {
|
||||
var rootFolderPath = RootFolders.get(parseInt(rootFolder, 10));
|
||||
|
||||
model.set('rootFolderPath', rootFolderPath.get('path'));
|
||||
}
|
||||
|
||||
model.edited = true;
|
||||
});
|
||||
|
||||
this.artistCollection.save();
|
||||
},
|
||||
|
||||
_updateInfo : function() {
|
||||
var selected = this.editorGrid.getSelectedModels();
|
||||
var selectedCount = selected.length;
|
||||
|
||||
this.ui.selectedCount.html('{0} artist selected'.format(selectedCount));
|
||||
|
||||
if (selectedCount === 0) {
|
||||
this.ui.actions.attr('disabled', 'disabled');
|
||||
} else {
|
||||
this.ui.actions.removeAttr('disabled');
|
||||
}
|
||||
},
|
||||
|
||||
_rootFolderChanged : function() {
|
||||
var rootFolderValue = this.ui.rootFolder.val();
|
||||
if (rootFolderValue === 'addNew') {
|
||||
var rootFolderLayout = new RootFolderLayout();
|
||||
this.listenToOnce(rootFolderLayout, 'folderSelected', this._setRootFolder);
|
||||
vent.trigger(vent.Commands.OpenModalCommand, rootFolderLayout);
|
||||
} else {
|
||||
Config.setValue(Config.Keys.DefaultRootFolderId, rootFolderValue);
|
||||
}
|
||||
},
|
||||
|
||||
_setRootFolder : function(options) {
|
||||
vent.trigger(vent.Commands.CloseModalCommand);
|
||||
this.ui.rootFolder.val(options.model.id);
|
||||
this._rootFolderChanged();
|
||||
},
|
||||
|
||||
_organizeFiles : function() {
|
||||
var selected = this.editorGrid.getSelectedModels();
|
||||
var updateFilesArtistView = new UpdateFilesArtistView({ artist : selected });
|
||||
this.listenToOnce(updateFilesArtistView, 'updatingFiles', this._afterSave);
|
||||
|
||||
vent.trigger(vent.Commands.OpenModalCommand, updateFilesSeriesView);
|
||||
}
|
||||
});
|
@ -0,0 +1,54 @@
|
||||
<div class="artist-editor-footer">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-2">
|
||||
<label>Monitored</label>
|
||||
|
||||
<select class="form-control x-action x-monitored">
|
||||
<option value="noChange">No change</option>
|
||||
<option value="true">Monitored</option>
|
||||
<option value="false">Unmonitored</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label>Profile</label>
|
||||
|
||||
<select class="form-control x-action x-profiles">
|
||||
<option value="noChange">No change</option>
|
||||
{{#each profiles.models}}
|
||||
<option value="{{id}}">{{attributes.name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label>Season Folder</label>
|
||||
|
||||
<select class="form-control x-action x-season-folder">
|
||||
<option value="noChange">No change</option>
|
||||
<option value="true">Yes</option>
|
||||
<option value="false">No</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-3">
|
||||
<label>Root Folder</label>
|
||||
|
||||
<select class="form-control x-action x-root-folder" validation-name="RootFolderPath">
|
||||
<option value="noChange">No change</option>
|
||||
{{#each rootFolders}}
|
||||
<option value="{{id}}">{{path}}</option>
|
||||
{{/each}}
|
||||
<option value="addNew">Add a different path</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-3 actions">
|
||||
<label class="x-selected-count">0 artist selected</label>
|
||||
<div>
|
||||
<button class="btn btn-primary x-action x-save">Save</button>
|
||||
<button class="btn btn-danger x-action x-organize-files" title="Organize and rename track files">Organize</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,185 @@
|
||||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
var Backgrid = require('backgrid');
|
||||
var EmptyView = require('../Index/EmptyView');
|
||||
var ArtistCollection = require('../ArtistCollection');
|
||||
var ArtistTitleCell = require('../../Cells/ArtistTitleCell');
|
||||
var ProfileCell = require('../../Cells/ProfileCell');
|
||||
var ArtistStatusCell = require('../../Cells/ArtistStatusCell');
|
||||
var ArtistFolderCell = require('../../Cells/ArtistFolderCell');
|
||||
var SelectAllCell = require('../../Cells/SelectAllCell');
|
||||
var ToolbarLayout = require('../../Shared/Toolbar/ToolbarLayout');
|
||||
var FooterView = require('./ArtistEditorFooterView');
|
||||
require('../../Mixins/backbone.signalr.mixin');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'Artist/Editor/ArtistEditorLayoutTemplate',
|
||||
|
||||
regions : {
|
||||
artistRegion : '#x-artist-editor',
|
||||
toolbar : '#x-toolbar'
|
||||
},
|
||||
|
||||
ui : {
|
||||
monitored : '.x-monitored',
|
||||
profiles : '.x-profiles',
|
||||
rootFolder : '.x-root-folder',
|
||||
selectedCount : '.x-selected-count'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-save' : '_updateAndSave',
|
||||
'change .x-root-folder' : '_rootFolderChanged'
|
||||
},
|
||||
|
||||
columns : [
|
||||
{
|
||||
name : '',
|
||||
cell : SelectAllCell,
|
||||
headerCell : 'select-all',
|
||||
sortable : false
|
||||
},
|
||||
{
|
||||
name : 'statusWeight',
|
||||
label : '',
|
||||
cell : ArtistStatusCell
|
||||
},
|
||||
{
|
||||
name : 'artistName',
|
||||
label : 'Artist',
|
||||
cell : ArtistTitleCell,
|
||||
cellValue : 'this'
|
||||
},
|
||||
{
|
||||
name : 'profileId',
|
||||
label : 'Profile',
|
||||
cell : ProfileCell
|
||||
},
|
||||
{
|
||||
name : 'artistFolder',
|
||||
label : 'Artist Folder',
|
||||
cell : ArtistFolderCell
|
||||
},
|
||||
{
|
||||
name : 'path',
|
||||
label : 'Path',
|
||||
cell : 'string'
|
||||
}
|
||||
],
|
||||
|
||||
leftSideButtons : {
|
||||
type : 'default',
|
||||
storeState : false,
|
||||
items : [
|
||||
{
|
||||
title : 'Season Pass',
|
||||
icon : 'icon-lidarr-monitored',
|
||||
route : 'seasonpass'
|
||||
},
|
||||
{
|
||||
title : 'Update Library',
|
||||
icon : 'icon-lidarr-refresh',
|
||||
command : 'refreshartist',
|
||||
successMessage : 'Library was updated!',
|
||||
errorMessage : 'Library update failed!'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
this.artistCollection = ArtistCollection.clone();
|
||||
this.artistCollection.bindSignalR();
|
||||
|
||||
this.listenTo(this.artistCollection, 'save', this.render);
|
||||
|
||||
this.filteringOptions = {
|
||||
type : 'radio',
|
||||
storeState : true,
|
||||
menuKey : 'artisteditor.filterMode',
|
||||
defaultAction : 'all',
|
||||
items : [
|
||||
{
|
||||
key : 'all',
|
||||
title : '',
|
||||
tooltip : 'All',
|
||||
icon : 'icon-lidarr-all',
|
||||
callback : this._setFilter
|
||||
},
|
||||
{
|
||||
key : 'monitored',
|
||||
title : '',
|
||||
tooltip : 'Monitored Only',
|
||||
icon : 'icon-lidarr-monitored',
|
||||
callback : this._setFilter
|
||||
},
|
||||
{
|
||||
key : 'continuing',
|
||||
title : '',
|
||||
tooltip : 'Continuing Only',
|
||||
icon : 'icon-lidarr-artist-continuing',
|
||||
callback : this._setFilter
|
||||
},
|
||||
{
|
||||
key : 'ended',
|
||||
title : '',
|
||||
tooltip : 'Ended Only',
|
||||
icon : 'icon-lidarr-artist-ended',
|
||||
callback : this._setFilter
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
this._showToolbar();
|
||||
this._showTable();
|
||||
},
|
||||
|
||||
onClose : function() {
|
||||
vent.trigger(vent.Commands.CloseControlPanelCommand);
|
||||
},
|
||||
|
||||
_showTable : function() {
|
||||
if (this.artistCollection.shadowCollection.length === 0) {
|
||||
this.artistRegion.show(new EmptyView());
|
||||
this.toolbar.close();
|
||||
return;
|
||||
}
|
||||
|
||||
this.columns[0].sortedCollection = this.artistCollection;
|
||||
|
||||
this.editorGrid = new Backgrid.Grid({
|
||||
collection : this.artistCollection,
|
||||
columns : this.columns,
|
||||
className : 'table table-hover'
|
||||
});
|
||||
|
||||
this.artistRegion.show(this.editorGrid);
|
||||
this._showFooter();
|
||||
},
|
||||
|
||||
_showToolbar : function() {
|
||||
this.toolbar.show(new ToolbarLayout({
|
||||
left : [
|
||||
this.leftSideButtons
|
||||
],
|
||||
right : [
|
||||
this.filteringOptions
|
||||
],
|
||||
context : this
|
||||
}));
|
||||
},
|
||||
|
||||
_showFooter : function() {
|
||||
vent.trigger(vent.Commands.OpenControlPanelCommand, new FooterView({
|
||||
editorGrid : this.editorGrid,
|
||||
collection : this.artistCollection
|
||||
}));
|
||||
},
|
||||
|
||||
_setFilter : function(buttonContext) {
|
||||
var mode = buttonContext.model.get('key');
|
||||
|
||||
this.artistCollection.setFilterMode(mode);
|
||||
}
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
<div id="x-toolbar"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="x-artist-editor" class="table-responsive"></div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,33 @@
|
||||
var _ = require('underscore');
|
||||
var vent = require('vent');
|
||||
var Backbone = require('backbone');
|
||||
var Marionette = require('marionette');
|
||||
var CommandController = require('../../../Commands/CommandController');
|
||||
|
||||
module.exports = Marionette.ItemView.extend({
|
||||
template : 'Artist/Editor/Organize/OrganizeFilesViewTemplate',
|
||||
|
||||
events : {
|
||||
'click .x-confirm-organize' : '_organize'
|
||||
},
|
||||
|
||||
initialize : function(options) {
|
||||
this.artist = options.artist;
|
||||
this.templateHelpers = {
|
||||
numberOfArtist : this.artist.length,
|
||||
artist : new Backbone.Collection(this.artist).toJSON()
|
||||
};
|
||||
},
|
||||
|
||||
_organize : function() {
|
||||
var artistIds = _.pluck(this.artist, 'id');
|
||||
|
||||
CommandController.Execute('renameArtist', {
|
||||
name : 'renameArtist',
|
||||
artistIds : artistIds
|
||||
});
|
||||
|
||||
this.trigger('organizingFiles');
|
||||
vent.trigger(vent.Commands.CloseModalCommand);
|
||||
}
|
||||
});
|
@ -0,0 +1,25 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>Organize of Selected Series</h3>
|
||||
</div>
|
||||
<div class="modal-body update-files-artist-modal">
|
||||
<div class="alert alert-info">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
Tip: To preview a rename... select "Cancel" then any artist title and use the <i data-original-title="" class="icon-lidarr-rename" title=""></i>
|
||||
</div>
|
||||
|
||||
Are you sure you want to update all files in the {{numberOfSeries}} selected artist?
|
||||
|
||||
{{debug}}
|
||||
<ul class="selected-artist">
|
||||
{{#each series}}
|
||||
<li>{{title}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal">Cancel</button>
|
||||
<button class="btn btn-danger x-confirm-organize">Organize</button>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,35 @@
|
||||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
var CommandController = require('../../Commands/CommandController');
|
||||
|
||||
module.exports = Marionette.ItemView.extend({
|
||||
ui : {
|
||||
refresh : '.x-refresh'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-edit' : '_editArtist',
|
||||
'click .x-refresh' : '_refreshArtist'
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
CommandController.bindToCommand({
|
||||
element : this.ui.refresh,
|
||||
command : {
|
||||
name : 'refreshArtist',
|
||||
seriesId : this.model.get('id')
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_editArtist : function() {
|
||||
vent.trigger(vent.Commands.EditArtistCommand, { artist : this.model });
|
||||
},
|
||||
|
||||
_refreshArtist : function() {
|
||||
CommandController.Execute('refreshArtist', {
|
||||
name : 'refreshArtist',
|
||||
seriesId : this.model.id
|
||||
});
|
||||
}
|
||||
});
|
@ -0,0 +1,357 @@
|
||||
var _ = require('underscore');
|
||||
var Marionette = require('marionette');
|
||||
var Backgrid = require('backgrid');
|
||||
var PosterCollectionView = require('./Posters/ArtistPostersCollectionView');
|
||||
var ListCollectionView = require('./Overview/ArtistOverviewCollectionView');
|
||||
var EmptyView = require('./EmptyView');
|
||||
var ArtistCollection = require('../ArtistCollection');
|
||||
var RelativeDateCell = require('../../Cells/RelativeDateCell');
|
||||
var ArtistTitleCell = require('../../Cells/ArtistTitleCell');
|
||||
var TemplatedCell = require('../../Cells/TemplatedCell');
|
||||
var ProfileCell = require('../../Cells/ProfileCell');
|
||||
var EpisodeProgressCell = require('../../Cells/EpisodeProgressCell');
|
||||
var ArtistActionsCell = require('../../Cells/ArtistActionsCell');
|
||||
var ArtistStatusCell = require('../../Cells/ArtistStatusCell');
|
||||
var FooterView = require('./FooterView');
|
||||
var FooterModel = require('./FooterModel');
|
||||
var ToolbarLayout = require('../../Shared/Toolbar/ToolbarLayout');
|
||||
require('../../Mixins/backbone.signalr.mixin');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'Artist/Index/ArtistIndexLayoutTemplate',
|
||||
|
||||
|
||||
regions : {
|
||||
artistRegion : '#x-artist',
|
||||
toolbar : '#x-toolbar',
|
||||
toolbar2 : '#x-toolbar2',
|
||||
footer : '#x-artist-footer'
|
||||
},
|
||||
|
||||
columns : [
|
||||
{
|
||||
name : 'statusWeight',
|
||||
label : '',
|
||||
cell : ArtistStatusCell
|
||||
},
|
||||
{
|
||||
name : 'title',
|
||||
label : 'Title',
|
||||
cell : ArtistTitleCell,
|
||||
cellValue : 'this',
|
||||
sortValue : 'sortTitle'
|
||||
},
|
||||
{
|
||||
name : 'albumCount',
|
||||
label : 'Albums',
|
||||
cell : 'integer'
|
||||
},
|
||||
{
|
||||
name : 'profileId',
|
||||
label : 'Profile',
|
||||
cell : ProfileCell
|
||||
},
|
||||
{
|
||||
name : 'network',
|
||||
label : 'Network',
|
||||
cell : 'string'
|
||||
},
|
||||
{
|
||||
name : 'nextAiring',
|
||||
label : 'Next Airing',
|
||||
cell : RelativeDateCell
|
||||
},
|
||||
{
|
||||
name : 'percentOfEpisodes',
|
||||
label : 'Tracks',
|
||||
cell : EpisodeProgressCell,
|
||||
className : 'episode-progress-cell'
|
||||
},
|
||||
{
|
||||
name : 'this',
|
||||
label : '',
|
||||
sortable : false,
|
||||
cell : ArtistActionsCell
|
||||
}
|
||||
],
|
||||
|
||||
leftSideButtons : {
|
||||
type : 'default',
|
||||
storeState : false,
|
||||
collapse : true,
|
||||
items : [
|
||||
{
|
||||
title : 'Add Artist',
|
||||
icon : 'icon-lidarr-add',
|
||||
route : 'addartist'
|
||||
},
|
||||
{
|
||||
title : 'Season Pass',
|
||||
icon : 'icon-lidarr-monitored',
|
||||
route : 'seasonpass'
|
||||
},
|
||||
{
|
||||
title : 'Artist Editor',
|
||||
icon : 'icon-lidarr-edit',
|
||||
route : 'artisteditor'
|
||||
},
|
||||
{
|
||||
title : 'RSS Sync',
|
||||
icon : 'icon-lidarr-rss',
|
||||
command : 'rsssync',
|
||||
errorMessage : 'RSS Sync Failed!'
|
||||
},
|
||||
{
|
||||
title : 'Update Library',
|
||||
icon : 'icon-lidarr-refresh',
|
||||
command : 'refreshartist',
|
||||
successMessage : 'Library was updated!',
|
||||
errorMessage : 'Library update failed!'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
this.artistCollection = ArtistCollection.clone();
|
||||
this.artistCollection.bindSignalR();
|
||||
|
||||
this.listenTo(this.artistCollection, 'sync', function(model, collection, options) {
|
||||
this.artistCollection.fullCollection.resetFiltered();
|
||||
this._renderView();
|
||||
});
|
||||
|
||||
this.listenTo(this.artistCollection, 'add', function(model, collection, options) {
|
||||
this.artistCollection.fullCollection.resetFiltered();
|
||||
this._renderView();
|
||||
});
|
||||
|
||||
this.listenTo(this.artistCollection, 'remove', function(model, collection, options) {
|
||||
this.artistCollection.fullCollection.resetFiltered();
|
||||
this._renderView();
|
||||
});
|
||||
|
||||
this.sortingOptions = {
|
||||
type : 'sorting',
|
||||
storeState : false,
|
||||
viewCollection : this.artistCollection,
|
||||
items : [
|
||||
{
|
||||
title : 'Title',
|
||||
name : 'title'
|
||||
},
|
||||
{
|
||||
title : 'Albums',
|
||||
name : 'albumCount'
|
||||
},
|
||||
{
|
||||
title : 'Quality',
|
||||
name : 'profileId'
|
||||
},
|
||||
{
|
||||
title : 'Network',
|
||||
name : 'network'
|
||||
},
|
||||
{
|
||||
title : 'Next Airing',
|
||||
name : 'nextAiring'
|
||||
},
|
||||
{
|
||||
title : 'Tracks',
|
||||
name : 'percentOfEpisodes'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.filteringOptions = {
|
||||
type : 'radio',
|
||||
storeState : true,
|
||||
menuKey : 'series.filterMode',
|
||||
defaultAction : 'all',
|
||||
items : [
|
||||
{
|
||||
key : 'all',
|
||||
title : '',
|
||||
tooltip : 'All',
|
||||
icon : 'icon-lidarr-all',
|
||||
callback : this._setFilter
|
||||
},
|
||||
{
|
||||
key : 'monitored',
|
||||
title : '',
|
||||
tooltip : 'Monitored Only',
|
||||
icon : 'icon-lidarr-monitored',
|
||||
callback : this._setFilter
|
||||
},
|
||||
{
|
||||
key : 'continuing',
|
||||
title : '',
|
||||
tooltip : 'Continuing Only',
|
||||
icon : 'icon-lidarr-artist-continuing',
|
||||
callback : this._setFilter
|
||||
},
|
||||
{
|
||||
key : 'ended',
|
||||
title : '',
|
||||
tooltip : 'Ended Only',
|
||||
icon : 'icon-lidarr-artist-ended',
|
||||
callback : this._setFilter
|
||||
},
|
||||
{
|
||||
key : 'missing',
|
||||
title : '',
|
||||
tooltip : 'Missing',
|
||||
icon : 'icon-lidarr-missing',
|
||||
callback : this._setFilter
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.viewButtons = {
|
||||
type : 'radio',
|
||||
storeState : true,
|
||||
menuKey : 'seriesViewMode',
|
||||
defaultAction : 'listView',
|
||||
items : [
|
||||
{
|
||||
key : 'posterView',
|
||||
title : '',
|
||||
tooltip : 'Posters',
|
||||
icon : 'icon-lidarr-view-poster',
|
||||
callback : this._showPosters
|
||||
},
|
||||
{
|
||||
key : 'listView',
|
||||
title : '',
|
||||
tooltip : 'Overview List',
|
||||
icon : 'icon-lidarr-view-list',
|
||||
callback : this._showList
|
||||
},
|
||||
{
|
||||
key : 'tableView',
|
||||
title : '',
|
||||
tooltip : 'Table',
|
||||
icon : 'icon-lidarr-view-table',
|
||||
callback : this._showTable
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
onShow : function() {
|
||||
this._showToolbar();
|
||||
this._fetchCollection();
|
||||
},
|
||||
|
||||
_showTable : function() {
|
||||
this.currentView = new Backgrid.Grid({
|
||||
collection : this.artistCollection,
|
||||
columns : this.columns,
|
||||
className : 'table table-hover'
|
||||
});
|
||||
|
||||
this._renderView();
|
||||
},
|
||||
|
||||
_showList : function() {
|
||||
this.currentView = new ListCollectionView({
|
||||
collection : this.artistCollection
|
||||
});
|
||||
|
||||
this._renderView();
|
||||
},
|
||||
|
||||
_showPosters : function() {
|
||||
this.currentView = new PosterCollectionView({
|
||||
collection : this.artistCollection
|
||||
});
|
||||
|
||||
this._renderView();
|
||||
},
|
||||
|
||||
_renderView : function() {
|
||||
// Problem is this is calling before artistCollection has updated. Where are the promises with backbone?
|
||||
if (this.artistCollection.length === 0) {
|
||||
this.artistRegion.show(new EmptyView());
|
||||
|
||||
this.toolbar.close();
|
||||
this.toolbar2.close();
|
||||
} else {
|
||||
this.artistRegion.show(this.currentView);
|
||||
|
||||
this._showToolbar();
|
||||
this._showFooter();
|
||||
}
|
||||
},
|
||||
|
||||
_fetchCollection : function() {
|
||||
this.artistCollection.fetch();
|
||||
console.log('index page, collection: ', this.artistCollection);
|
||||
},
|
||||
|
||||
_setFilter : function(buttonContext) {
|
||||
var mode = buttonContext.model.get('key');
|
||||
|
||||
this.artistCollection.setFilterMode(mode);
|
||||
},
|
||||
|
||||
_showToolbar : function() {
|
||||
if (this.toolbar.currentView) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.toolbar2.show(new ToolbarLayout({
|
||||
right : [
|
||||
this.filteringOptions
|
||||
],
|
||||
context : this
|
||||
}));
|
||||
|
||||
this.toolbar.show(new ToolbarLayout({
|
||||
right : [
|
||||
this.sortingOptions,
|
||||
this.viewButtons
|
||||
],
|
||||
left : [
|
||||
this.leftSideButtons
|
||||
],
|
||||
context : this
|
||||
}));
|
||||
},
|
||||
|
||||
_showFooter : function() {
|
||||
var footerModel = new FooterModel();
|
||||
var artist = this.artistCollection.models.length;
|
||||
var episodes = 0;
|
||||
var episodeFiles = 0;
|
||||
var ended = 0;
|
||||
var continuing = 0;
|
||||
var monitored = 0;
|
||||
|
||||
_.each(this.artistCollection.models, function(model) {
|
||||
episodes += model.get('episodeCount'); // TODO: Refactor to Seasons and Tracks
|
||||
episodeFiles += model.get('episodeFileCount');
|
||||
|
||||
/*if (model.get('status').toLowerCase() === 'ended') {
|
||||
ended++;
|
||||
} else {
|
||||
continuing++;
|
||||
}*/
|
||||
|
||||
if (model.get('monitored')) {
|
||||
monitored++;
|
||||
}
|
||||
});
|
||||
|
||||
footerModel.set({
|
||||
artist : artist,
|
||||
ended : ended,
|
||||
continuing : continuing,
|
||||
monitored : monitored,
|
||||
unmonitored : artist - monitored,
|
||||
episodes : episodes,
|
||||
episodeFiles : episodeFiles
|
||||
});
|
||||
|
||||
this.footer.show(new FooterView({ model : footerModel }));
|
||||
}
|
||||
});
|
@ -0,0 +1,12 @@
|
||||
<div class="toolbars">
|
||||
<div id="x-toolbar"></div>
|
||||
<div id="x-toolbar2"></div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="x-artist" class="table-responsive"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="x-artist-footer"></div>
|
@ -0,0 +1,16 @@
|
||||
<div class="no-artist">
|
||||
<div class="row">
|
||||
<div class="well col-md-12">
|
||||
<i class="icon-lidarr-comment"/>
|
||||
You must be new around here. You should add some music.
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<a href="/addartist" class='btn btn-lg btn-block btn-success x-add-artist'>
|
||||
<i class='icon-lidarr-add'></i>
|
||||
Add Music
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,5 @@
|
||||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
template : 'Artist/Index/EmptyTemplate'
|
||||
});
|
@ -0,0 +1,4 @@
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = Backbone.Model.extend({});
|
@ -0,0 +1,5 @@
|
||||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
template : 'Artist/Index/FooterViewTemplate'
|
||||
});
|
@ -0,0 +1,46 @@
|
||||
<div class="row">
|
||||
<div class="artist-legend legend col-xs-6 col-sm-4">
|
||||
<ul class='legend-labels'>
|
||||
<li><span class="progress-bar"></span>Continuing (All tracks downloaded)</li>
|
||||
<li><span class="progress-bar-success"></span>Ended (All tracks downloaded)</li>
|
||||
<li><span class="progress-bar-danger"></span>Missing Tracks (Artist monitored)</li>
|
||||
<li><span class="progress-bar-warning"></span>Missing Tracks (Artist not monitored)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-xs-5 col-sm-7">
|
||||
<div class="row">
|
||||
<div class="artist-stats col-sm-4">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Artists</dt>
|
||||
<dd>{{artist}}</dd>
|
||||
|
||||
<dt>Ended</dt>
|
||||
<dd>{{ended}}</dd>
|
||||
|
||||
<dt>Continuing</dt>
|
||||
<dd>{{continuing}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="artist-stats col-sm-4">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Monitored</dt>
|
||||
<dd>{{monitored}}</dd>
|
||||
|
||||
<dt>Unmonitored</dt>
|
||||
<dd>{{unmonitored}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="artist-stats col-sm-4">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Tracks</dt>
|
||||
<dd>{{episodes}}</dd>
|
||||
|
||||
<dt>Files</dt>
|
||||
<dd>{{episodeFiles}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,8 @@
|
||||
var Marionette = require('marionette');
|
||||
var ListItemView = require('./ArtistOverviewItemView');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
itemView : ListItemView,
|
||||
itemViewContainer : '#x-artist-list',
|
||||
template : 'Artist/Index/Overview/ArtistOverviewCollectionViewTemplate'
|
||||
});
|
@ -0,0 +1 @@
|
||||
<div id="x-artist-list"/>
|
@ -0,0 +1,7 @@
|
||||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
var ArtistIndexItemView = require('../ArtistIndexItemView');
|
||||
|
||||
module.exports = ArtistIndexItemView.extend({
|
||||
template : 'Artist/Index/Overview/ArtistOverviewItemViewTemplate'
|
||||
});
|
@ -0,0 +1,59 @@
|
||||
<div class="artist-item">
|
||||
<div class="row">
|
||||
<div class="col-md-2 col-xs-3">
|
||||
<a href="{{route}}">
|
||||
{{poster}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-10 col-xs-9">
|
||||
<div class="row">
|
||||
<div class="col-md-10 col-xs-10">
|
||||
<a href="{{route}}" target="_blank">
|
||||
<h2>{{artistName}}</h2>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-2">
|
||||
<div class="pull-right artist-overview-list-actions">
|
||||
<i class="icon-lidarr-refresh x-refresh" title="Update artist info and scan disk"/>
|
||||
<i class="icon-lidarr-edit x-edit" title="Edit Artist"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-10 col-xs-12">
|
||||
<div>
|
||||
{{truncate overview 600}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-10 col-xs-8">
|
||||
<!--{{#if_eq status compare="ended"}}
|
||||
<span class="label label-danger">Ended</span>
|
||||
{{/if_eq}}-->
|
||||
|
||||
<!--
|
||||
NOTE: We can show next drop date of album in future
|
||||
{{#if nextAiring}}
|
||||
<span class="label label-default">{{RelativeDate nextAiring}}</span>
|
||||
{{/if}}-->
|
||||
|
||||
{{albumCountHelper}}
|
||||
|
||||
{{profile profileId}}
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-4">
|
||||
{{> EpisodeProgressPartial }}
|
||||
</div>
|
||||
<div class="col-md-8 col-xs-10">
|
||||
Path {{path}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,8 @@
|
||||
var Marionette = require('marionette');
|
||||
var PosterItemView = require('./ArtistPostersItemView');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
itemView : PosterItemView,
|
||||
itemViewContainer : '#x-artist-posters',
|
||||
template : 'Artist/Index/Posters/ArtistPostersCollectionViewTemplate'
|
||||
});
|
@ -0,0 +1 @@
|
||||
<ul id="x-artist-posters" class="artist-posters"></ul>
|
@ -0,0 +1,19 @@
|
||||
var ArtistIndexItemView = require('../ArtistIndexItemView');
|
||||
|
||||
module.exports = ArtistIndexItemView.extend({
|
||||
tagName : 'li',
|
||||
template : 'Artist/Index/Posters/ArtistPostersItemViewTemplate',
|
||||
|
||||
initialize : function() {
|
||||
this.events['mouseenter .x-artist-poster-container'] = 'posterHoverAction';
|
||||
this.events['mouseleave .x-artist-poster-container'] = 'posterHoverAction';
|
||||
|
||||
this.ui.controls = '.x-artist-controls';
|
||||
this.ui.title = '.x-title';
|
||||
},
|
||||
|
||||
posterHoverAction : function() {
|
||||
this.ui.controls.slideToggle();
|
||||
this.ui.title.slideToggle();
|
||||
}
|
||||
});
|
@ -0,0 +1,30 @@
|
||||
<div class="artist-posters-item">
|
||||
<div class="center">
|
||||
<div class="artist-poster-container x-artist-poster-container">
|
||||
<div class="artist-controls x-artist-controls">
|
||||
<i class="icon-lidarr-refresh x-refresh" title="Refresh Artist"/>
|
||||
<i class="icon-lidarr-edit x-edit" title="Edit Artist"/>
|
||||
</div>
|
||||
{{#unless_eq status compare="continuing"}}
|
||||
<div class="ended-banner">Ended</div>
|
||||
{{/unless_eq}}
|
||||
<a href="{{route}}">
|
||||
{{poster}}
|
||||
<div class="center title">{{title}}</div>
|
||||
</a>
|
||||
<div class="hidden-title x-title">
|
||||
{{title}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="center">
|
||||
<div class="labels">
|
||||
{{> EpisodeProgressPartial }}
|
||||
|
||||
{{#if nextAiring}}
|
||||
<span class="label label-default">{{RelativeDate nextAiring}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,4 @@
|
||||
<div class="progress episode-progress">
|
||||
<span class="progressbar-back-text">{{episodeFileCount}} / {{episodeCount}}</span>
|
||||
<div class="progress-bar {{EpisodeProgressClass}} episode-progress" style="width:{{percentOfEpisodes}}%"><span class="progressbar-front-text">{{episodeFileCount}} / {{episodeCount}}</span></div>
|
||||
</div>
|
@ -0,0 +1,62 @@
|
||||
var Backbone = require('backbone');
|
||||
var PageableCollection = require('backbone.pageable');
|
||||
var TrackModel = require('./TrackModel');
|
||||
require('./TrackCollection');
|
||||
|
||||
module.exports = PageableCollection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/episode',
|
||||
model : TrackModel,
|
||||
|
||||
state : {
|
||||
sortKey : 'episodeNumber',
|
||||
order : 1,
|
||||
pageSize : 100000
|
||||
},
|
||||
|
||||
mode : 'client',
|
||||
|
||||
originalFetch : Backbone.Collection.prototype.fetch,
|
||||
|
||||
initialize : function(options) {
|
||||
this.seriesId = options.seriesId;
|
||||
},
|
||||
|
||||
bySeason : function(season) {
|
||||
var filtered = this.filter(function(episode) {
|
||||
return episode.get('seasonNumber') === season;
|
||||
});
|
||||
|
||||
var EpisodeCollection = require('./TrackCollection');
|
||||
|
||||
return new EpisodeCollection(filtered);
|
||||
},
|
||||
|
||||
comparator : function(model1, model2) {
|
||||
var episode1 = model1.get('episodeNumber');
|
||||
var episode2 = model2.get('episodeNumber');
|
||||
|
||||
if (episode1 < episode2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (episode1 > episode2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
|
||||
fetch : function(options) {
|
||||
if (!this.seriesId) {
|
||||
throw 'seriesId is required';
|
||||
}
|
||||
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
options.data = { seriesId : this.seriesId };
|
||||
|
||||
return this.originalFetch.call(this, options);
|
||||
}
|
||||
});
|
@ -0,0 +1,28 @@
|
||||
var Backbone = require('backbone');
|
||||
var TrackFileModel = require('./TrackFileModel');
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/episodefile',
|
||||
model : TrackFileModel,
|
||||
|
||||
originalFetch : Backbone.Collection.prototype.fetch,
|
||||
|
||||
initialize : function(options) {
|
||||
this.artistId = options.artistId;
|
||||
this.models = [];
|
||||
},
|
||||
|
||||
fetch : function(options) {
|
||||
if (!this.artistId) {
|
||||
throw 'artistId is required';
|
||||
}
|
||||
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
options.data = { seriesId : this.seriesId };
|
||||
|
||||
return this.originalFetch.call(this, options);
|
||||
}
|
||||
});
|
@ -0,0 +1,3 @@
|
||||
var Backbone = require('backbone');
|
||||
|
||||
module.exports = Backbone.Model.extend({});
|
@ -0,0 +1,20 @@
|
||||
var Backbone = require('backbone');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults : {
|
||||
seasonNumber : 0,
|
||||
status : 0
|
||||
},
|
||||
|
||||
methodUrls : {
|
||||
'update' : window.NzbDrone.ApiRoot + '/episode'
|
||||
},
|
||||
|
||||
sync : function(method, model, options) {
|
||||
if (model.methodUrls && model.methodUrls[method.toLowerCase()]) {
|
||||
options = options || {};
|
||||
options.url = model.methodUrls[method.toLowerCase()];
|
||||
}
|
||||
return Backbone.sync(method, model, options);
|
||||
}
|
||||
});
|
@ -0,0 +1,477 @@
|
||||
@import "../Content/Bootstrap/variables";
|
||||
@import "../Shared/Styles/card.less";
|
||||
@import "../Shared/Styles/clickable.less";
|
||||
@import "../Content/prefixer";
|
||||
|
||||
.artist-poster {
|
||||
min-width: 56px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.truncate {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.edit-artist-modal, .delete-artist-modal {
|
||||
overflow : visible;
|
||||
|
||||
.artist-poster {
|
||||
padding-left : 20px;
|
||||
width : 168px;
|
||||
}
|
||||
|
||||
.form-horizontal {
|
||||
margin-top : 10px;
|
||||
}
|
||||
|
||||
.twitter-typeahead {
|
||||
.form-control[disabled] {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.delete-artist-modal {
|
||||
.path {
|
||||
margin-left : 30px;
|
||||
}
|
||||
|
||||
.delete-files-info {
|
||||
margin-top : 10px;
|
||||
display : none;
|
||||
}
|
||||
}
|
||||
|
||||
.artist-item {
|
||||
padding-bottom : 30px;
|
||||
|
||||
:hover {
|
||||
text-decoration : none;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top : 0px;
|
||||
}
|
||||
|
||||
a {
|
||||
color : #000000;
|
||||
}
|
||||
}
|
||||
|
||||
.artist-page-header {
|
||||
.card(black);
|
||||
.opacity(0.9);
|
||||
background : #000000;
|
||||
color : #ffffff;
|
||||
padding : 30px 15px;
|
||||
margin : 50px 10px;
|
||||
|
||||
.poster {
|
||||
margin-top : 4px;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
margin-top : 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.artist-album {
|
||||
.card;
|
||||
.opacity(0.9);
|
||||
margin : 30px 10px;
|
||||
padding : 10px 25px;
|
||||
|
||||
.show-hide-episodes {
|
||||
.clickable();
|
||||
text-align : center;
|
||||
|
||||
i {
|
||||
.clickable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.artist-posters {
|
||||
list-style-type: none;
|
||||
|
||||
@media (max-width: @screen-xs-max) {
|
||||
padding : 0px;
|
||||
}
|
||||
|
||||
li {
|
||||
display : inline-block;
|
||||
vertical-align : top;
|
||||
}
|
||||
|
||||
.artist-posters-item {
|
||||
|
||||
.card;
|
||||
.clickable;
|
||||
margin-bottom : 20px;
|
||||
height : 315px;
|
||||
|
||||
.center {
|
||||
display : block;
|
||||
margin-left : auto;
|
||||
margin-right : auto;
|
||||
text-align : center;
|
||||
|
||||
.progress {
|
||||
text-align : left;
|
||||
margin-top : 5px;
|
||||
left : 0px;
|
||||
width : 170px;
|
||||
|
||||
.progressbar-front-text, .progressbar-back-text {
|
||||
width : 170px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.labels {
|
||||
display : inline-block;
|
||||
.opacity(0.75);
|
||||
width : 170px;
|
||||
|
||||
:hover {
|
||||
cursor : default;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-top : 3px;
|
||||
display : block;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
.opacity(1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: @screen-xs-max) {
|
||||
height : 235px;
|
||||
margin : 5px;
|
||||
padding : 6px 5px;
|
||||
|
||||
.center {
|
||||
.progress {
|
||||
width : 125px;
|
||||
|
||||
.progressbar-front-text, .progressbar-back-text {
|
||||
width : 125px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.labels {
|
||||
width: 125px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.artist-poster-container {
|
||||
position : relative;
|
||||
overflow : hidden;
|
||||
display : inline-block;
|
||||
|
||||
.placeholder-image ~ .title {
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
.title {
|
||||
position : absolute;
|
||||
top : 25px;
|
||||
color : #f5f5f5;
|
||||
width : 100%;
|
||||
font-size : 22px;
|
||||
line-height: 24px;
|
||||
opacity : 0.0;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.ended-banner {
|
||||
color : #eeeeee;
|
||||
background-color : #b94a48;
|
||||
.box-shadow(2px 2px 20px #888888);
|
||||
-moz-transform-origin : 50% 50%;
|
||||
-webkit-transform-origin : 50% 50%;
|
||||
position : absolute;
|
||||
width : 320px;
|
||||
top : 200px;
|
||||
left : -122px;
|
||||
text-align : center;
|
||||
.opacity(0.9);
|
||||
|
||||
.transform(rotate(45deg));
|
||||
}
|
||||
|
||||
.artist-controls {
|
||||
position : absolute;;
|
||||
top : 0px;
|
||||
overflow : hidden;
|
||||
background-color : #eeeeee;
|
||||
width : 100%;
|
||||
text-align : right;
|
||||
padding-right : 10px;
|
||||
display : none;
|
||||
.opacity(0.8);
|
||||
|
||||
i {
|
||||
.clickable();
|
||||
}
|
||||
}
|
||||
|
||||
.hidden-title {
|
||||
position : absolute;;
|
||||
bottom : 0px;
|
||||
overflow : hidden;
|
||||
background-color : #eeeeee;
|
||||
width : 100%;
|
||||
text-align : center;
|
||||
.opacity(0.8);
|
||||
display : none;
|
||||
}
|
||||
|
||||
.artist-poster {
|
||||
width : 168px;
|
||||
height : 247px;
|
||||
display : block;
|
||||
font-size : 34px;
|
||||
line-height : 34px;
|
||||
}
|
||||
|
||||
@media (max-width: @screen-xs-max) {
|
||||
.artist-poster {
|
||||
width : 120px;
|
||||
height : 176px;
|
||||
}
|
||||
|
||||
.ended-banner {
|
||||
top : 145px;
|
||||
left : -137px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.artist-detail-overview {
|
||||
margin-bottom : 50px;
|
||||
}
|
||||
|
||||
.artist-album {
|
||||
|
||||
.episode-number-cell {
|
||||
width : 40px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.episode-air-date-cell {
|
||||
width : 150px;
|
||||
}
|
||||
|
||||
.episode-status-cell {
|
||||
width : 100px;
|
||||
}
|
||||
|
||||
.episode-title-cell {
|
||||
cursor : pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.episode-detail-modal {
|
||||
|
||||
.episode-info {
|
||||
margin-bottom : 10px;
|
||||
}
|
||||
|
||||
.episode-overview {
|
||||
font-style : italic;
|
||||
}
|
||||
|
||||
.episode-file-info {
|
||||
margin-top : 30px;
|
||||
font-size : 12px;
|
||||
}
|
||||
|
||||
.episode-history-details-cell .popover {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.hidden-artist-title {
|
||||
display : none;
|
||||
}
|
||||
}
|
||||
|
||||
.album-grid {
|
||||
.toggle-cell {
|
||||
width : 28px;
|
||||
text-align : center;
|
||||
padding-left : 0px;
|
||||
padding-right : 0px;
|
||||
}
|
||||
|
||||
.toggle-cell {
|
||||
i {
|
||||
.clickable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.album-actions {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.album-actions, .artist-actions {
|
||||
|
||||
div {
|
||||
display : inline-block
|
||||
}
|
||||
|
||||
text-transform : none;
|
||||
|
||||
i {
|
||||
.clickable();
|
||||
font-size : 24px;
|
||||
margin-left : 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.artist-stats {
|
||||
font-size : 11px;
|
||||
}
|
||||
|
||||
.artist-legend {
|
||||
padding-top : 5px;
|
||||
}
|
||||
|
||||
.albumpass-artist {
|
||||
.card;
|
||||
margin : 20px 0px;
|
||||
|
||||
.title {
|
||||
font-weight : 300;
|
||||
font-size : 24px;
|
||||
line-height : 30px;
|
||||
margin-left : 5px;
|
||||
}
|
||||
|
||||
.album-select {
|
||||
margin-bottom : 0px;
|
||||
}
|
||||
|
||||
.expander {
|
||||
.clickable;
|
||||
line-height : 30px;
|
||||
margin-left : 8px;
|
||||
width : 16px;
|
||||
}
|
||||
|
||||
.album-grid {
|
||||
margin-top : 10px;
|
||||
}
|
||||
|
||||
.album-pass-button {
|
||||
display : inline-block;
|
||||
}
|
||||
|
||||
.artist-monitor-toggle {
|
||||
font-size : 24px;
|
||||
margin-top : 3px;
|
||||
}
|
||||
|
||||
.help-inline {
|
||||
margin-top : 7px;
|
||||
display : inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.album-status {
|
||||
font-size : 11px;
|
||||
vertical-align : middle !important;
|
||||
}
|
||||
|
||||
//Overview List
|
||||
.artist-overview-list-actions {
|
||||
min-width: 56px;
|
||||
max-width: 56px;
|
||||
|
||||
i {
|
||||
.clickable();
|
||||
}
|
||||
}
|
||||
|
||||
//Editor
|
||||
|
||||
.artist-editor-footer {
|
||||
max-width: 1160px;
|
||||
color: #f5f5f5;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
.form-group {
|
||||
padding-top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.update-files-artist-modal {
|
||||
.selected-artist {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
//artist Details
|
||||
|
||||
.artist-not-monitored {
|
||||
.album-monitored, .episode-monitored {
|
||||
color: #888888;
|
||||
cursor: not-allowed;
|
||||
|
||||
i {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.artist-info {
|
||||
.row {
|
||||
margin-bottom : 3px;
|
||||
|
||||
.label {
|
||||
display : inline-block;
|
||||
margin-bottom : 2px;
|
||||
padding : 4px 6px 3px 6px;
|
||||
max-width : 100%;
|
||||
white-space : normal;
|
||||
word-wrap : break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.artist-info-links {
|
||||
@media (max-width: @screen-sm-max) {
|
||||
display : inline-block;
|
||||
margin-top : 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scene-info {
|
||||
.key, .value {
|
||||
display : inline-block;
|
||||
}
|
||||
|
||||
.key {
|
||||
width : 80px;
|
||||
margin-left : 10px;
|
||||
vertical-align : top;
|
||||
}
|
||||
|
||||
.value {
|
||||
margin-right : 10px;
|
||||
max-width : 170px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left : 0px;
|
||||
list-style-type : none;
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
var vent = require('vent');
|
||||
var NzbDroneCell = require('./NzbDroneCell');
|
||||
var CommandController = require('../Commands/CommandController');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'artist-actions-cell',
|
||||
|
||||
ui : {
|
||||
refresh : '.x-refresh'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-edit' : '_editArtist',
|
||||
'click .x-refresh' : '_refreshArtist'
|
||||
},
|
||||
|
||||
render : function() {
|
||||
this.$el.empty();
|
||||
|
||||
this.$el.html('<i class="icon-lidarr-refresh x-refresh hidden-xs" title="" data-original-title="Update artist info and scan disk"></i> ' +
|
||||
'<i class="icon-lidarr-edit x-edit" title="" data-original-title="Edit Artist"></i>');
|
||||
|
||||
CommandController.bindToCommand({
|
||||
element : this.$el.find('.x-refresh'),
|
||||
command : {
|
||||
name : 'refreshArtist',
|
||||
seriesId : this.model.get('id')
|
||||
}
|
||||
});
|
||||
|
||||
this.delegateEvents();
|
||||
return this;
|
||||
},
|
||||
|
||||
_editArtist : function() {
|
||||
vent.trigger(vent.Commands.EditArtistCommand, { artist : this.model });
|
||||
},
|
||||
|
||||
_refreshArtist : function() {
|
||||
CommandController.Execute('refreshArtist', {
|
||||
name : 'refreshArtist',
|
||||
artistId : this.model.id
|
||||
});
|
||||
}
|
||||
});
|
@ -0,0 +1,14 @@
|
||||
var Backgrid = require('backgrid');
|
||||
|
||||
module.exports = Backgrid.Cell.extend({
|
||||
className : 'artist-folder-cell',
|
||||
|
||||
render : function() {
|
||||
this.$el.empty();
|
||||
|
||||
var artistFolder = this.model.get(this.column.get('name'));
|
||||
this.$el.html(artistFolder.toString());
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
@ -0,0 +1,32 @@
|
||||
var NzbDroneCell = require('./NzbDroneCell');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'artist-status-cell',
|
||||
|
||||
render : function() {
|
||||
this.$el.empty();
|
||||
var monitored = this.model.get('monitored');
|
||||
var status = this.model.get('status');
|
||||
|
||||
if (status === 'ended') {
|
||||
this.$el.html('<i class="icon-lidarr-artist-ended grid-icon" title="Ended"></i>');
|
||||
this._setStatusWeight(3);
|
||||
}
|
||||
|
||||
else if (!monitored) {
|
||||
this.$el.html('<i class="icon-lidarr-artist-unmonitored grid-icon" title="Not Monitored"></i>');
|
||||
this._setStatusWeight(2);
|
||||
}
|
||||
|
||||
else {
|
||||
this.$el.html('<i class="icon-lidarr-artist-continuing grid-icon" title="Continuing"></i>');
|
||||
this._setStatusWeight(1);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_setStatusWeight : function(weight) {
|
||||
this.model.set('statusWeight', weight, { silent : true });
|
||||
}
|
||||
});
|
@ -0,0 +1,6 @@
|
||||
var TemplatedCell = require('./TemplatedCell');
|
||||
|
||||
module.exports = TemplatedCell.extend({
|
||||
className : 'artist-title-cell',
|
||||
template : 'Cells/ArtistTitleTemplate'
|
||||
});
|
@ -0,0 +1 @@
|
||||
<a href="{{route}}">{{artistName}}</a>
|
@ -0,0 +1,106 @@
|
||||
var Handlebars = require('handlebars');
|
||||
var StatusModel = require('../../System/StatusModel');
|
||||
var _ = require('underscore');
|
||||
|
||||
Handlebars.registerHelper('poster', function() {
|
||||
|
||||
var placeholder = StatusModel.get('urlBase') + '/Content/Images/poster-dark.png';
|
||||
var poster = _.where(this.images, { coverType : 'poster' });
|
||||
|
||||
if (poster[0]) {
|
||||
if (!poster[0].url.match(/^https?:\/\//)) {
|
||||
return new Handlebars.SafeString('<img class="series-poster x-series-poster" {0}>'.format(Handlebars.helpers.defaultImg.call(null, poster[0].url, 250)));
|
||||
} else {
|
||||
var url = poster[0].url.replace(/^https?\:/, '');
|
||||
return new Handlebars.SafeString('<img class="series-poster x-series-poster" {0}>'.format(Handlebars.helpers.defaultImg.call(null, url)));
|
||||
}
|
||||
}
|
||||
|
||||
return new Handlebars.SafeString('<img class="series-poster placeholder-image" src="{0}">'.format(placeholder));
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('traktUrl', function() {
|
||||
return 'http://trakt.tv/search/tvdb/' + this.tvdbId + '?id_type=show';
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('imdbUrl', function() {
|
||||
return 'http://imdb.com/title/' + this.imdbId;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('tvdbUrl', function() {
|
||||
return 'http://www.thetvdb.com/?tab=series&id=' + this.tvdbId;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('tvRageUrl', function() {
|
||||
return 'http://www.tvrage.com/shows/id-' + this.tvRageId;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('tvMazeUrl', function() {
|
||||
return 'http://www.tvmaze.com/shows/' + this.tvMazeId + '/_';
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('route', function() {
|
||||
return StatusModel.get('urlBase') + '/artist/' + this.titleSlug;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('percentOfEpisodes', function() {
|
||||
var episodeCount = this.episodeCount;
|
||||
var episodeFileCount = this.episodeFileCount;
|
||||
|
||||
var percent = 100;
|
||||
|
||||
if (episodeCount > 0) {
|
||||
percent = episodeFileCount / episodeCount * 100;
|
||||
}
|
||||
|
||||
return percent;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('seasonCountHelper', function() {
|
||||
var seasonCount = this.seasonCount;
|
||||
var continuing = this.status === 'continuing';
|
||||
|
||||
if (continuing) {
|
||||
return new Handlebars.SafeString('<span class="label label-info">Season {0}</span>'.format(seasonCount));
|
||||
}
|
||||
|
||||
if (seasonCount === 1) {
|
||||
return new Handlebars.SafeString('<span class="label label-info">{0} Season</span>'.format(seasonCount));
|
||||
}
|
||||
|
||||
return new Handlebars.SafeString('<span class="label label-info">{0} Seasons</span>'.format(seasonCount));
|
||||
});
|
||||
|
||||
Handlebars.registerHelper ('truncate', function (str, len) {
|
||||
if (str && str.length > len && str.length > 0) {
|
||||
var new_str = str + " ";
|
||||
new_str = str.substr (0, len);
|
||||
new_str = str.substr (0, new_str.lastIndexOf(" "));
|
||||
new_str = (new_str.length > 0) ? new_str : str.substr (0, len);
|
||||
|
||||
return new Handlebars.SafeString ( new_str +'...' );
|
||||
}
|
||||
return str;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('albumCountHelper', function() {
|
||||
var albumCount = this.albumCount;
|
||||
|
||||
if (albumCount === 1) {
|
||||
return new Handlebars.SafeString('<span class="label label-info">{0} Albums</span>'.format(albumCount));
|
||||
}
|
||||
|
||||
return new Handlebars.SafeString('<span class="label label-info">{0} Albums</span>'.format(albumCount));
|
||||
});
|
||||
|
||||
/*Handlebars.registerHelper('titleWithYear', function() {
|
||||
if (this.title.endsWith(' ({0})'.format(this.year))) {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
if (!this.year) {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
return new Handlebars.SafeString('{0} <span class="year">({1})</span>'.format(this.title, this.year));
|
||||
});*/
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue