diff --git a/gulp/less.js b/gulp/less.js
index 76e04b8dc..0baf2eb31 100644
--- a/gulp/less.js
+++ b/gulp/less.js
@@ -17,8 +17,10 @@ gulp.task('less', function() {
paths.src.content + 'theme.less',
paths.src.content + 'overrides.less',
paths.src.root + 'Series/series.less',
+ paths.src.root + 'Artist/artist.less',
paths.src.root + 'Activity/activity.less',
paths.src.root + 'AddSeries/addSeries.less',
+ paths.src.root + 'AddArtist/addArtist.less',
paths.src.root + 'Calendar/calendar.less',
paths.src.root + 'Cells/cells.less',
paths.src.root + 'ManualImport/manualimport.less',
diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs b/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs
index b03e16cd4..6fafae599 100644
--- a/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs
+++ b/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs
@@ -25,7 +25,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
Host = "localhost";
Port = 8112;
Password = "deluge";
- TvCategory = "tv-Lidarr";
+ TvCategory = "lidarr";
}
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
@@ -43,10 +43,10 @@ namespace NzbDrone.Core.Download.Clients.Deluge
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
public string TvCategory { get; set; }
- [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
+ [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
public int RecentTvPriority { get; set; }
- [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
+ [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
public int OlderTvPriority { get; set; }
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)]
diff --git a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs
index 9a4561fa0..b4e22fe91 100644
--- a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs
+++ b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs
@@ -29,7 +29,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
{
Host = "localhost";
Port = 4321;
- TvCategory = "TV Shows";
+ TvCategory = "Music";
RecentTvPriority = (int)NzbVortexPriority.Normal;
OlderTvPriority = (int)NzbVortexPriority.Normal;
}
@@ -46,10 +46,10 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
[FieldDefinition(3, Label = "Group", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
public string TvCategory { get; set; }
- [FieldDefinition(4, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
+ [FieldDefinition(4, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
public int RecentTvPriority { get; set; }
- [FieldDefinition(5, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
+ [FieldDefinition(5, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
public int OlderTvPriority { get; set; }
public NzbDroneValidationResult Validate()
diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs
index 3b20b010c..8a75be249 100644
--- a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs
+++ b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs
@@ -26,7 +26,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
{
Host = "localhost";
Port = 6789;
- TvCategory = "tv";
+ TvCategory = "Music";
+ Username = "nzbget";
+ Password = "tegbzn6789";
RecentTvPriority = (int)NzbgetPriority.Normal;
OlderTvPriority = (int)NzbgetPriority.Normal;
}
@@ -46,10 +48,10 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
public string TvCategory { get; set; }
- [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
+ [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
public int RecentTvPriority { get; set; }
- [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
+ [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
public int OlderTvPriority { get; set; }
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)]
diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs
index aeddcbf3a..6bdffa80c 100644
--- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs
+++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs
@@ -22,7 +22,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
{
Host = "localhost";
Port = 9091;
- TvCategory = "tv-Lidarr";
+ TvCategory = "lidarr";
}
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
@@ -40,10 +40,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
public string TvCategory { get; set; }
- [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
+ [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
public int RecentTvPriority { get; set; }
- [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
+ [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
public int OlderTvPriority { get; set; }
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use a secure connection. See Options -> Web UI -> 'Use HTTPS instead of HTTP' in qBittorrent.")]
diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs
index 374079134..ce86d3773 100644
--- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs
+++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
Host = "localhost";
Port = 8080;
- TvCategory = "tv";
+ TvCategory = "music";
RecentTvPriority = (int)SabnzbdPriority.Default;
OlderTvPriority = (int)SabnzbdPriority.Default;
}
@@ -61,10 +61,10 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
public string TvCategory { get; set; }
- [FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
+ [FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
public int RecentTvPriority { get; set; }
- [FieldDefinition(7, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
+ [FieldDefinition(7, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
public int OlderTvPriority { get; set; }
[FieldDefinition(8, Label = "Use SSL", Type = FieldType.Checkbox)]
diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs
index 38f2a60a1..9626eb7f7 100644
--- a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs
+++ b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs
@@ -56,10 +56,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission
[FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Transmission location")]
public string TvDirectory { get; set; }
- [FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
+ [FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
public int RecentTvPriority { get; set; }
- [FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
+ [FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
public int OlderTvPriority { get; set; }
[FieldDefinition(9, Label = "Use SSL", Type = FieldType.Checkbox)]
diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs
index 7e964db7a..07a5c9b6f 100644
--- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs
+++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs
@@ -26,7 +26,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
Host = "localhost";
Port = 8080;
UrlBase = "RPC2";
- TvCategory = "tv-Lidarr";
+ TvCategory = "lidarr";
OlderTvPriority = (int)RTorrentPriority.Normal;
RecentTvPriority = (int)RTorrentPriority.Normal;
}
@@ -55,10 +55,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
[FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default rTorrent location")]
public string TvDirectory { get; set; }
- [FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
+ [FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
public int RecentTvPriority { get; set; }
- [FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
+ [FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
public int OlderTvPriority { get; set; }
public NzbDroneValidationResult Validate()
diff --git a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs
index e76406084..103bec26e 100644
--- a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs
+++ b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs
@@ -23,7 +23,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
{
Host = "localhost";
Port = 9091;
- TvCategory = "tv-Lidarr";
+ TvCategory = "lidarr";
}
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
@@ -41,10 +41,10 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
public string TvCategory { get; set; }
- [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
+ [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
public int RecentTvPriority { get; set; }
- [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
+ [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
public int OlderTvPriority { get; set; }
public NzbDroneValidationResult Validate()
diff --git a/src/NzbDrone.Core/Download/TorrentClientBase.cs b/src/NzbDrone.Core/Download/TorrentClientBase.cs
index 609cd3bd6..61986ce8f 100644
--- a/src/NzbDrone.Core/Download/TorrentClientBase.cs
+++ b/src/NzbDrone.Core/Download/TorrentClientBase.cs
@@ -154,7 +154,7 @@ namespace NzbDrone.Core.Download
torrentFile = response.ResponseData;
- _logger.Debug("Downloading torrent for episode '{0}' finished ({1} bytes from {2})", remoteEpisode.Release.Title, torrentFile.Length, torrentUrl);
+ _logger.Debug("Downloading torrent for release '{0}' finished ({1} bytes from {2})", remoteEpisode.Release.Title, torrentFile.Length, torrentUrl);
}
catch (HttpException ex)
{
@@ -164,14 +164,14 @@ namespace NzbDrone.Core.Download
}
else
{
- _logger.Error(ex, "Downloading torrent file for episode '{0}' failed ({1})", remoteEpisode.Release.Title, torrentUrl);
+ _logger.Error(ex, "Downloading torrent file for release '{0}' failed ({1})", remoteEpisode.Release.Title, torrentUrl);
}
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading torrent failed", ex);
}
catch (WebException ex)
{
- _logger.Error(ex, "Downloading torrent file for episode '{0}' failed ({1})", remoteEpisode.Release.Title, torrentUrl);
+ _logger.Error(ex, "Downloading torrent file for release '{0}' failed ({1})", remoteEpisode.Release.Title, torrentUrl);
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading torrent failed", ex);
}
@@ -201,7 +201,7 @@ namespace NzbDrone.Core.Download
}
catch (FormatException ex)
{
- _logger.Error(ex, "Failed to parse magnetlink for episode '{0}': '{1}'", remoteEpisode.Release.Title, magnetUrl);
+ _logger.Error(ex, "Failed to parse magnetlink for release '{0}': '{1}'", remoteEpisode.Release.Title, magnetUrl);
return null;
}
diff --git a/src/NzbDrone.Core/Download/UsenetClientBase.cs b/src/NzbDrone.Core/Download/UsenetClientBase.cs
index a6c0ed7d5..1d50dd178 100644
--- a/src/NzbDrone.Core/Download/UsenetClientBase.cs
+++ b/src/NzbDrone.Core/Download/UsenetClientBase.cs
@@ -42,7 +42,7 @@ namespace NzbDrone.Core.Download
{
nzbData = _httpClient.Get(new HttpRequest(url)).ResponseData;
- _logger.Debug("Downloaded nzb for episode '{0}' finished ({1} bytes from {2})", remoteEpisode.Release.Title, nzbData.Length, url);
+ _logger.Debug("Downloaded nzb for release '{0}' finished ({1} bytes from {2})", remoteEpisode.Release.Title, nzbData.Length, url);
}
catch (HttpException ex)
{
@@ -52,14 +52,14 @@ namespace NzbDrone.Core.Download
}
else
{
- _logger.Error(ex, "Downloading nzb for episode '{0}' failed ({1})", remoteEpisode.Release.Title, url);
+ _logger.Error(ex, "Downloading nzb for release '{0}' failed ({1})", remoteEpisode.Release.Title, url);
}
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading nzb failed", ex);
}
catch (WebException ex)
{
- _logger.Error(ex, "Downloading nzb for episode '{0}' failed ({1})", remoteEpisode.Release.Title, url);
+ _logger.Error(ex, "Downloading nzb for release '{0}' failed ({1})", remoteEpisode.Release.Title, url);
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading nzb failed", ex);
}
diff --git a/src/UI/.idea/runConfigurations/Debug___Chrome.xml b/src/UI/.idea/runConfigurations/Debug___Chrome.xml
index 659f9572b..2ff8dbf6b 100644
--- a/src/UI/.idea/runConfigurations/Debug___Chrome.xml
+++ b/src/UI/.idea/runConfigurations/Debug___Chrome.xml
@@ -10,7 +10,7 @@
-
+
diff --git a/src/UI/.idea/runConfigurations/Debug___Firefox.xml b/src/UI/.idea/runConfigurations/Debug___Firefox.xml
index 8646368d1..dbbdebbe4 100644
--- a/src/UI/.idea/runConfigurations/Debug___Firefox.xml
+++ b/src/UI/.idea/runConfigurations/Debug___Firefox.xml
@@ -9,7 +9,7 @@
-
+
diff --git a/src/UI/AddArtist/AddArtistCollection.js b/src/UI/AddArtist/AddArtistCollection.js
new file mode 100644
index 000000000..a243649f4
--- /dev/null
+++ b/src/UI/AddArtist/AddArtistCollection.js
@@ -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;
+ }
+});
\ No newline at end of file
diff --git a/src/UI/AddArtist/AddArtistLayout.js b/src/UI/AddArtist/AddArtistLayout.js
new file mode 100644
index 000000000..2b398335e
--- /dev/null
+++ b/src/UI/AddArtist/AddArtistLayout.js
@@ -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());
+ }
+});
\ No newline at end of file
diff --git a/src/UI/AddArtist/AddArtistLayoutTemplate.hbs b/src/UI/AddArtist/AddArtistLayoutTemplate.hbs
new file mode 100644
index 000000000..53f225a1e
--- /dev/null
+++ b/src/UI/AddArtist/AddArtistLayoutTemplate.hbs
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Import existing artists on disk
+
+ Add New Artist
+
+
+
+
+
diff --git a/src/UI/AddArtist/AddArtistView.js b/src/UI/AddArtist/AddArtistView.js
new file mode 100644
index 000000000..85e47a02d
--- /dev/null
+++ b/src/UI/AddArtist/AddArtistView.js
@@ -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 = '';
+ }
+ }
+});
\ No newline at end of file
diff --git a/src/UI/AddArtist/AddArtistViewTemplate.hbs b/src/UI/AddArtist/AddArtistViewTemplate.hbs
new file mode 100644
index 000000000..adadf0569
--- /dev/null
+++ b/src/UI/AddArtist/AddArtistViewTemplate.hbs
@@ -0,0 +1,24 @@
+{{#if folder.path}}
+{{/if}}
+
+
+
+
+ more
+
diff --git a/src/UI/AddArtist/ArtistTypeSelectionPartial.hbs b/src/UI/AddArtist/ArtistTypeSelectionPartial.hbs
new file mode 100644
index 000000000..f8cadd547
--- /dev/null
+++ b/src/UI/AddArtist/ArtistTypeSelectionPartial.hbs
@@ -0,0 +1,3 @@
+
+ Standard
+
diff --git a/src/UI/AddArtist/EmptyView.js b/src/UI/AddArtist/EmptyView.js
new file mode 100644
index 000000000..e07b1647d
--- /dev/null
+++ b/src/UI/AddArtist/EmptyView.js
@@ -0,0 +1,5 @@
+var Marionette = require('marionette');
+
+module.exports = Marionette.CompositeView.extend({
+ template : 'AddArtist/EmptyViewTemplate'
+});
\ No newline at end of file
diff --git a/src/UI/AddArtist/EmptyViewTemplate.hbs b/src/UI/AddArtist/EmptyViewTemplate.hbs
new file mode 100644
index 000000000..26c712271
--- /dev/null
+++ b/src/UI/AddArtist/EmptyViewTemplate.hbs
@@ -0,0 +1,3 @@
+
+ You can also search by Spotify using the spotify: prefixes.
+
diff --git a/src/UI/AddArtist/ErrorView.js b/src/UI/AddArtist/ErrorView.js
new file mode 100644
index 000000000..9d53fae8c
--- /dev/null
+++ b/src/UI/AddArtist/ErrorView.js
@@ -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;
+ }
+});
\ No newline at end of file
diff --git a/src/UI/AddArtist/ErrorViewTemplate.hbs b/src/UI/AddArtist/ErrorViewTemplate.hbs
new file mode 100644
index 000000000..c0b1e3673
--- /dev/null
+++ b/src/UI/AddArtist/ErrorViewTemplate.hbs
@@ -0,0 +1,7 @@
+
+
+ There was an error searching for '{{term}}'.
+
+
+ If the artist name contains non-alphanumeric characters try removing them, otherwise try your search again later.
+
diff --git a/src/UI/AddArtist/Existing/AddExistingArtistCollectionView.js b/src/UI/AddArtist/Existing/AddExistingArtistCollectionView.js
new file mode 100644
index 000000000..af57bc1d2
--- /dev/null
+++ b/src/UI/AddArtist/Existing/AddExistingArtistCollectionView.js
@@ -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
+ }
+
+});
\ No newline at end of file
diff --git a/src/UI/AddArtist/Existing/AddExistingArtistCollectionViewTemplate.hbs b/src/UI/AddArtist/Existing/AddExistingArtistCollectionViewTemplate.hbs
new file mode 100644
index 000000000..5acbd1ef0
--- /dev/null
+++ b/src/UI/AddArtist/Existing/AddExistingArtistCollectionViewTemplate.hbs
@@ -0,0 +1,5 @@
+
+
+ Loading search results from server for your artists, this may take a few minutes.
+
+
\ No newline at end of file
diff --git a/src/UI/AddArtist/Existing/UnmappedFolderCollection.js b/src/UI/AddArtist/Existing/UnmappedFolderCollection.js
new file mode 100644
index 000000000..bd2a83f49
--- /dev/null
+++ b/src/UI/AddArtist/Existing/UnmappedFolderCollection.js
@@ -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);
+ }
+});
\ No newline at end of file
diff --git a/src/UI/AddArtist/Existing/UnmappedFolderModel.js b/src/UI/AddArtist/Existing/UnmappedFolderModel.js
new file mode 100644
index 000000000..3986a5948
--- /dev/null
+++ b/src/UI/AddArtist/Existing/UnmappedFolderModel.js
@@ -0,0 +1,3 @@
+var Backbone = require('backbone');
+
+module.exports = Backbone.Model.extend({});
\ No newline at end of file
diff --git a/src/UI/AddArtist/MonitoringTooltipTemplate.hbs b/src/UI/AddArtist/MonitoringTooltipTemplate.hbs
new file mode 100644
index 000000000..0c795cf12
--- /dev/null
+++ b/src/UI/AddArtist/MonitoringTooltipTemplate.hbs
@@ -0,0 +1,18 @@
+
+ All
+ Monitor all tracks except specials
+ Future
+ Monitor tracks that have not been released yet
+ Missing
+ Monitor tracks that do not have files or have not aired yet
+ Existing
+ Monitor tracks that have files or have not aired yet
+ First Season
+ Monitor all tracks of the first album. All other albums will be ignored
+ Latest Season
+ Monitor all tracks of the latest album and future albums
+ None
+ No tracks will be monitored.
+
+
+
\ No newline at end of file
diff --git a/src/UI/AddArtist/NotFoundView.js b/src/UI/AddArtist/NotFoundView.js
new file mode 100644
index 000000000..d25f339c3
--- /dev/null
+++ b/src/UI/AddArtist/NotFoundView.js
@@ -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;
+ }
+});
\ No newline at end of file
diff --git a/src/UI/AddArtist/NotFoundViewTemplate.hbs b/src/UI/AddArtist/NotFoundViewTemplate.hbs
new file mode 100644
index 000000000..abaca6646
--- /dev/null
+++ b/src/UI/AddArtist/NotFoundViewTemplate.hbs
@@ -0,0 +1,7 @@
+
+
+ Sorry. We couldn't find any artist matching '{{term}}'
+
+
Why can't I find my artist?
+
+
diff --git a/src/UI/AddArtist/RootFolders/RootFolderCollection.js b/src/UI/AddArtist/RootFolders/RootFolderCollection.js
new file mode 100644
index 000000000..81050c19d
--- /dev/null
+++ b/src/UI/AddArtist/RootFolders/RootFolderCollection.js
@@ -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();
\ No newline at end of file
diff --git a/src/UI/AddArtist/RootFolders/RootFolderCollectionView.js b/src/UI/AddArtist/RootFolders/RootFolderCollectionView.js
new file mode 100644
index 000000000..1029de245
--- /dev/null
+++ b/src/UI/AddArtist/RootFolders/RootFolderCollectionView.js
@@ -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
+});
\ No newline at end of file
diff --git a/src/UI/AddArtist/RootFolders/RootFolderCollectionViewTemplate.hbs b/src/UI/AddArtist/RootFolders/RootFolderCollectionViewTemplate.hbs
new file mode 100644
index 000000000..70755bbca
--- /dev/null
+++ b/src/UI/AddArtist/RootFolders/RootFolderCollectionViewTemplate.hbs
@@ -0,0 +1,13 @@
+
+
+
+
+ Path
+
+
+ Free Space
+
+
+
+
+
\ No newline at end of file
diff --git a/src/UI/AddArtist/RootFolders/RootFolderItemView.js b/src/UI/AddArtist/RootFolders/RootFolderItemView.js
new file mode 100644
index 000000000..c22f6fcf7
--- /dev/null
+++ b/src/UI/AddArtist/RootFolders/RootFolderItemView.js
@@ -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);
+ }
+});
\ No newline at end of file
diff --git a/src/UI/AddArtist/RootFolders/RootFolderItemViewTemplate.hbs b/src/UI/AddArtist/RootFolders/RootFolderItemViewTemplate.hbs
new file mode 100644
index 000000000..c1378207a
--- /dev/null
+++ b/src/UI/AddArtist/RootFolders/RootFolderItemViewTemplate.hbs
@@ -0,0 +1,9 @@
+
+ {{path}}
+
+
+ {{Bytes freeSpace}}
+
+
+
+
diff --git a/src/UI/AddArtist/RootFolders/RootFolderLayout.js b/src/UI/AddArtist/RootFolders/RootFolderLayout.js
new file mode 100644
index 000000000..7b5036689
--- /dev/null
+++ b/src/UI/AddArtist/RootFolders/RootFolderLayout.js
@@ -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;
diff --git a/src/UI/AddArtist/RootFolders/RootFolderLayoutTemplate.hbs b/src/UI/AddArtist/RootFolders/RootFolderLayoutTemplate.hbs
new file mode 100644
index 000000000..efb6eb7c9
--- /dev/null
+++ b/src/UI/AddArtist/RootFolders/RootFolderLayoutTemplate.hbs
@@ -0,0 +1,36 @@
+
+
+
+
+
Enter the path that contains some or all of your Music, you will be able to choose which artist you want to import×
+
+
+
+
+
+ {{#if items}}
+
Recent Folders
+ {{/if}}
+
+
+
+
+
+
diff --git a/src/UI/AddArtist/RootFolders/RootFolderModel.js b/src/UI/AddArtist/RootFolders/RootFolderModel.js
new file mode 100644
index 000000000..28681768b
--- /dev/null
+++ b/src/UI/AddArtist/RootFolders/RootFolderModel.js
@@ -0,0 +1,8 @@
+var Backbone = require('backbone');
+
+module.exports = Backbone.Model.extend({
+ urlRoot : window.NzbDrone.ApiRoot + '/rootfolder',
+ defaults : {
+ freeSpace : 0
+ }
+});
\ No newline at end of file
diff --git a/src/UI/AddArtist/RootFolders/RootFolderSelectionPartial.hbs b/src/UI/AddArtist/RootFolders/RootFolderSelectionPartial.hbs
new file mode 100644
index 000000000..56729b0dd
--- /dev/null
+++ b/src/UI/AddArtist/RootFolders/RootFolderSelectionPartial.hbs
@@ -0,0 +1,11 @@
+
+ {{#if this}}
+ {{#each this}}
+ {{path}}
+ {{/each}}
+ {{else}}
+ Select Path
+ {{/if}}
+ Add a different path
+
+
diff --git a/src/UI/AddArtist/SearchResultCollectionView.js b/src/UI/AddArtist/SearchResultCollectionView.js
new file mode 100644
index 000000000..e533085ac
--- /dev/null
+++ b/src/UI/AddArtist/SearchResultCollectionView.js
@@ -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);
+ }
+ }
+});
\ No newline at end of file
diff --git a/src/UI/AddArtist/SearchResultView.js b/src/UI/AddArtist/SearchResultView.js
new file mode 100644
index 000000000..febd1612d
--- /dev/null
+++ b/src/UI/AddArtist/SearchResultView.js
@@ -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;
diff --git a/src/UI/AddArtist/SearchResultViewTemplate.hbs b/src/UI/AddArtist/SearchResultViewTemplate.hbs
new file mode 100644
index 000000000..0377ccbc6
--- /dev/null
+++ b/src/UI/AddArtist/SearchResultViewTemplate.hbs
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+ {{artistName}}
+
+
+
+
+
+
+
+ {{#unless existing}}
+ {{#unless path}}
+
+ Path
+ {{> RootFolderSelectionPartial rootFolders}}
+
+ {{/unless}}
+
+
+ Monitor
+
+ All
+ Future
+ Missing
+ Existing
+ None
+
+
+
+
+ Profile
+ {{> ProfileSelectionPartial profiles}}
+
+
+
+
+
+ {{/unless}}
+
+
+ {{#unless existing}}
+ {{#if artistName}}
+
+ {{else}}
+
+
+ Add
+
+
+ {{/if}}
+ {{else}}
+
+ {{/unless}}
+
+
+
+
+ {{#each albums}}
+
+
+
+
{{albumName}} ({{year}})
+ {{#unless existing}}
+ {{#if albumName}}
+
+ {{else}}
+
+
+ Add
+
+
+ {{/if}}
+ {{else}}
+
+ {{/unless}}
+
+
+ {{/each}}
+
+
diff --git a/src/UI/AddArtist/StartingAlbumSelectionPartial.hbs b/src/UI/AddArtist/StartingAlbumSelectionPartial.hbs
new file mode 100644
index 000000000..573599dab
--- /dev/null
+++ b/src/UI/AddArtist/StartingAlbumSelectionPartial.hbs
@@ -0,0 +1,13 @@
+
+
+
+ {{#each this}}
+ {{#if_eq seasonNumber compare="0"}}
+ Specials
+ {{else}}
+ Album {{seasonNumber}}
+ {{/if_eq}}
+ {{/each}}
+
+ None
+
diff --git a/src/UI/AddArtist/addArtist.less b/src/UI/AddArtist/addArtist.less
new file mode 100644
index 000000000..e53ff8eee
--- /dev/null
+++ b/src/UI/AddArtist/addArtist.less
@@ -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();
+ }
+}
diff --git a/src/UI/Artist/AlbumCollection.js b/src/UI/Artist/AlbumCollection.js
new file mode 100644
index 000000000..61faa2046
--- /dev/null
+++ b/src/UI/Artist/AlbumCollection.js
@@ -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');
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/AlbumModel.js b/src/UI/Artist/AlbumModel.js
new file mode 100644
index 000000000..1ba049eb6
--- /dev/null
+++ b/src/UI/Artist/AlbumModel.js
@@ -0,0 +1,11 @@
+var Backbone = require('backbone');
+
+module.exports = Backbone.Model.extend({
+ defaults : {
+ seasonNumber : 0
+ },
+
+ initialize : function() {
+ this.set('id', this.get('seasonNumber'));
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/ArtistCollection.js b/src/UI/Artist/ArtistCollection.js
index f9908032d..4bffc07e2 100644
--- a/src/UI/Artist/ArtistCollection.js
+++ b/src/UI/Artist/ArtistCollection.js
@@ -119,6 +119,6 @@ Collection = AsFilteredCollection.call(Collection);
Collection = AsSortedCollection.call(Collection);
Collection = AsPersistedStateCollection.call(Collection);
-var data = ApiData.get('series'); // TOOD: Build backend for artist
+var data = ApiData.get('artist'); // TOOD: Build backend for artist
module.exports = new Collection(data, { full : true }).bindSignalR();
diff --git a/src/UI/Artist/ArtistController.js b/src/UI/Artist/ArtistController.js
index 4805bd9bf..c6e24a771 100644
--- a/src/UI/Artist/ArtistController.js
+++ b/src/UI/Artist/ArtistController.js
@@ -1,8 +1,8 @@
var NzbDroneController = require('../Shared/NzbDroneController');
var AppLayout = require('../AppLayout');
var ArtistCollection = require('./ArtistCollection');
-var SeriesIndexLayout = require('../Series/Index/SeriesIndexLayout');
-var SeriesDetailsLayout = require('../Series/Details/SeriesDetailsLayout');
+var ArtistIndexLayout = require('./Index/ArtistIndexLayout');
+var ArtistDetailsLayout = require('./Details/ArtistDetailsLayout');
module.exports = NzbDroneController.extend({
_originalInit : NzbDroneController.prototype.initialize,
@@ -17,18 +17,18 @@ module.exports = NzbDroneController.extend({
artist : function() {
this.setTitle('Lidarr');
- this.showMainRegion(new SeriesIndexLayout());
+ this.showMainRegion(new ArtistIndexLayout());
},
artistDetails : function(query) {
- var artists = ArtistCollection.where({ artistNameSlug : query });
+ var artists = ArtistCollection.where({ artistSlug : query });
console.log('artistDetails, artists: ', artists);
if (artists.length !== 0) {
- var targetSeries = artists[0];
- console.log("[ArtistController] targetSeries: ", targetSeries);
- this.setTitle(targetSeries.get('artistName')); // TODO: Update NzbDroneController
+ var targetArtist = artists[0];
+ console.log("[ArtistController] targetArtist: ", targetArtist);
+ this.setTitle(targetArtist.get('artistName')); // TODO: Update NzbDroneController
//this.setArtistName(targetSeries.get('artistName'));
- this.showMainRegion(new SeriesDetailsLayout({ model : targetSeries }));
+ this.showMainRegion(new ArtistDetailsLayout({ model : targetArtist }));
} else {
this.showNotFound();
}
diff --git a/src/UI/Artist/ArtistModel.js b/src/UI/Artist/ArtistModel.js
index 209ebc1fa..a3c3dde50 100644
--- a/src/UI/Artist/ArtistModel.js
+++ b/src/UI/Artist/ArtistModel.js
@@ -13,7 +13,7 @@ module.exports = Backbone.Model.extend({
setAlbumsMonitored : function(albumName) {
_.each(this.get('albums'), function(album) {
- if (season.albumName === albumName) {
+ if (album.albumName === albumName) {
album.monitored = !album.monitored;
}
});
diff --git a/src/UI/Artist/Delete/DeleteArtistTemplate.hbs b/src/UI/Artist/Delete/DeleteArtistTemplate.hbs
new file mode 100644
index 000000000..5e13328b1
--- /dev/null
+++ b/src/UI/Artist/Delete/DeleteArtistTemplate.hbs
@@ -0,0 +1,50 @@
+
diff --git a/src/UI/Artist/Delete/DeleteArtistView.js b/src/UI/Artist/Delete/DeleteArtistView.js
new file mode 100644
index 000000000..de6640b5e
--- /dev/null
+++ b/src/UI/Artist/Delete/DeleteArtistView.js
@@ -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();
+ }
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Details/AlbumCollectionView.js b/src/UI/Artist/Details/AlbumCollectionView.js
new file mode 100644
index 000000000..133dff7c5
--- /dev/null
+++ b/src/UI/Artist/Details/AlbumCollectionView.js
@@ -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;
\ No newline at end of file
diff --git a/src/UI/Artist/Details/AlbumLayout.js b/src/UI/Artist/Details/AlbumLayout.js
new file mode 100644
index 000000000..733f54b6f
--- /dev/null
+++ b/src/UI/Artist/Details/AlbumLayout.js
@@ -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);
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Details/AlbumLayoutTemplate.hbs b/src/UI/Artist/Details/AlbumLayoutTemplate.hbs
new file mode 100644
index 000000000..685c8b97c
--- /dev/null
+++ b/src/UI/Artist/Details/AlbumLayoutTemplate.hbs
@@ -0,0 +1,50 @@
+
+
+
+
+ {{#if seasonNumber}}
+ Season {{seasonNumber}}
+ {{else}}
+ Specials
+ {{/if}}
+
+
+ {{#if_eq episodeCount compare=0}}
+ {{#if monitored}}
+
+ {{else}}
+
+ {{/if}}
+ {{else}}
+ {{#if_eq percentOfEpisodes compare=100}}
+ {{episodeFileCount}} / {{episodeCount}}
+ {{else}}
+ {{episodeFileCount}} / {{episodeCount}}
+ {{/if_eq}}
+ {{/if_eq}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{#if showingEpisodes}}
+
+ Hide Episodes
+ {{else}}
+
+ Show Episodes
+ {{/if}}
+
+
+
+
diff --git a/src/UI/Artist/Details/ArtistDetailsLayout.js b/src/UI/Artist/Details/ArtistDetailsLayout.js
new file mode 100644
index 000000000..67d55c306
--- /dev/null
+++ b/src/UI/Artist/Details/ArtistDetailsLayout.js
@@ -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');
+ }
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Details/ArtistDetailsTemplate.hbs b/src/UI/Artist/Details/ArtistDetailsTemplate.hbs
new file mode 100644
index 000000000..605ead424
--- /dev/null
+++ b/src/UI/Artist/Details/ArtistDetailsTemplate.hbs
@@ -0,0 +1,35 @@
+
+
diff --git a/src/UI/Artist/Details/InfoView.js b/src/UI/Artist/Details/InfoView.js
new file mode 100644
index 000000000..c7fab9fc4
--- /dev/null
+++ b/src/UI/Artist/Details/InfoView.js
@@ -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
+ };
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Details/InfoViewTemplate.hbs b/src/UI/Artist/Details/InfoViewTemplate.hbs
new file mode 100644
index 000000000..b52130246
--- /dev/null
+++ b/src/UI/Artist/Details/InfoViewTemplate.hbs
@@ -0,0 +1,73 @@
+
+
+ {{profile profileId}}
+
+ {{#if network}}
+ {{network}}
+ {{/if}}
+
+ {{runtime}} minutes
+ {{path}}
+
+ {{#if ratings}}
+ {{ratings.value}}
+ {{/if}}
+
+ {{Bytes sizeOnDisk}}
+
+ {{#if_eq fileCount compare="1"}}
+ 1 file
+ {{else}}
+ {{fileCount}} files
+ {{/if_eq}}
+
+ {{#if_eq status compare="continuing"}}
+ Continuing
+ {{else}}
+ Ended
+ {{/if_eq}}
+
+
+
+
+{{#if alternateTitles}}
+
+
+ {{#each alternateTitles}}
+ {{#if_eq seasonNumber compare="-1"}}
+ {{title}}
+ {{/if_eq}}
+
+ {{#if_eq sceneSeasonNumber compare="-1"}}
+ {{title}}
+ {{/if_eq}}
+ {{/each}}
+
+
+{{/if}}
+
+{{#if tags}}
+
+
+ {{tagDisplay tags}}
+
+
+{{/if}}
diff --git a/src/UI/Artist/Details/TrackNumberCell.js b/src/UI/Artist/Details/TrackNumberCell.js
new file mode 100644
index 000000000..a597c5c8a
--- /dev/null
+++ b/src/UI/Artist/Details/TrackNumberCell.js
@@ -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;
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Details/TrackNumberCellTemplate.hbs b/src/UI/Artist/Details/TrackNumberCellTemplate.hbs
new file mode 100644
index 000000000..a9028a423
--- /dev/null
+++ b/src/UI/Artist/Details/TrackNumberCellTemplate.hbs
@@ -0,0 +1,39 @@
+
+ {{#if sceneSeasonNumber}}
+
+
Season
+
{{sceneSeasonNumber}}
+
+ {{/if}}
+
+ {{#if sceneEpisodeNumber}}
+
+
Episode
+
{{sceneEpisodeNumber}}
+
+ {{/if}}
+
+ {{#if sceneAbsoluteEpisodeNumber}}
+
+
Absolute
+
{{sceneAbsoluteEpisodeNumber}}
+
+ {{/if}}
+
+ {{#if alternateTitles}}
+
+ {{#if_gt alternateTitles.length compare="1"}}
+
Titles
+ {{else}}
+
Title
+ {{/if_gt}}
+
+
+ {{#each alternateTitles}}
+ {{title}}
+ {{/each}}
+
+
+
+ {{/if}}
+
\ No newline at end of file
diff --git a/src/UI/Artist/Details/TrackWarningCell.js b/src/UI/Artist/Details/TrackWarningCell.js
new file mode 100644
index 000000000..22098d1a8
--- /dev/null
+++ b/src/UI/Artist/Details/TrackWarningCell.js
@@ -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(' ');
+ }
+
+ else if (ArtistCollection.get(this.model.get('artistId')).get('artistType') === 'anime' && this.model.get('seasonNumber') > 0 && !this.model.has('absoluteEpisodeNumber')) {
+ this.$el.html(' ');
+ }
+
+ this.delegateEvents();
+ return this;
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Edit/EditArtistView.js b/src/UI/Artist/Edit/EditArtistView.js
new file mode 100644
index 000000000..c4240cc1c
--- /dev/null
+++ b/src/UI/Artist/Edit/EditArtistView.js
@@ -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;
\ No newline at end of file
diff --git a/src/UI/Artist/Edit/EditArtistViewTemplate.hbs b/src/UI/Artist/Edit/EditArtistViewTemplate.hbs
new file mode 100644
index 000000000..c727554cc
--- /dev/null
+++ b/src/UI/Artist/Edit/EditArtistViewTemplate.hbs
@@ -0,0 +1,104 @@
+
diff --git a/src/UI/Artist/Editor/ArtistEditorFooterView.js b/src/UI/Artist/Editor/ArtistEditorFooterView.js
new file mode 100644
index 000000000..b32fdc7cc
--- /dev/null
+++ b/src/UI/Artist/Editor/ArtistEditorFooterView.js
@@ -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, updateFilesArtistView);
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Editor/ArtistEditorFooterViewTemplate.hbs b/src/UI/Artist/Editor/ArtistEditorFooterViewTemplate.hbs
new file mode 100644
index 000000000..91a9aaea7
--- /dev/null
+++ b/src/UI/Artist/Editor/ArtistEditorFooterViewTemplate.hbs
@@ -0,0 +1,54 @@
+
diff --git a/src/UI/Artist/Editor/ArtistEditorLayout.js b/src/UI/Artist/Editor/ArtistEditorLayout.js
new file mode 100644
index 000000000..51f0ec52e
--- /dev/null
+++ b/src/UI/Artist/Editor/ArtistEditorLayout.js
@@ -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);
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Editor/ArtistEditorLayoutTemplate.hbs b/src/UI/Artist/Editor/ArtistEditorLayoutTemplate.hbs
new file mode 100644
index 000000000..17746236f
--- /dev/null
+++ b/src/UI/Artist/Editor/ArtistEditorLayoutTemplate.hbs
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/src/UI/Artist/Editor/Organize/OrganizeFilesView.js b/src/UI/Artist/Editor/Organize/OrganizeFilesView.js
new file mode 100644
index 000000000..b18ba784a
--- /dev/null
+++ b/src/UI/Artist/Editor/Organize/OrganizeFilesView.js
@@ -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);
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Editor/Organize/OrganizeFilesViewTemplate.hbs b/src/UI/Artist/Editor/Organize/OrganizeFilesViewTemplate.hbs
new file mode 100644
index 000000000..9729b7fdf
--- /dev/null
+++ b/src/UI/Artist/Editor/Organize/OrganizeFilesViewTemplate.hbs
@@ -0,0 +1,25 @@
+
+
+
+
+ ×
+ Tip: To preview a rename... select "Cancel" then any artist title and use the
+
+
+ Are you sure you want to update all files in the {{numberOfSeries}} selected artist?
+
+ {{debug}}
+
+ {{#each series}}
+ {{title}}
+ {{/each}}
+
+
+
+
diff --git a/src/UI/Artist/Index/ArtistIndexItemView.js b/src/UI/Artist/Index/ArtistIndexItemView.js
new file mode 100644
index 000000000..df8c1379b
--- /dev/null
+++ b/src/UI/Artist/Index/ArtistIndexItemView.js
@@ -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
+ });
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Index/ArtistIndexLayout.js b/src/UI/Artist/Index/ArtistIndexLayout.js
new file mode 100644
index 000000000..b06bd5311
--- /dev/null
+++ b/src/UI/Artist/Index/ArtistIndexLayout.js
@@ -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 }));
+ }
+});
diff --git a/src/UI/Artist/Index/ArtistIndexLayoutTemplate.hbs b/src/UI/Artist/Index/ArtistIndexLayoutTemplate.hbs
new file mode 100644
index 000000000..ac13a764c
--- /dev/null
+++ b/src/UI/Artist/Index/ArtistIndexLayoutTemplate.hbs
@@ -0,0 +1,12 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/UI/Artist/Index/EmptyTemplate.hbs b/src/UI/Artist/Index/EmptyTemplate.hbs
new file mode 100644
index 000000000..ebb59426b
--- /dev/null
+++ b/src/UI/Artist/Index/EmptyTemplate.hbs
@@ -0,0 +1,16 @@
+
+
+
+
+ You must be new around here. You should add some music.
+
+
+
+
diff --git a/src/UI/Artist/Index/EmptyView.js b/src/UI/Artist/Index/EmptyView.js
new file mode 100644
index 000000000..4ad54762c
--- /dev/null
+++ b/src/UI/Artist/Index/EmptyView.js
@@ -0,0 +1,5 @@
+var Marionette = require('marionette');
+
+module.exports = Marionette.CompositeView.extend({
+ template : 'Artist/Index/EmptyTemplate'
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Index/FooterModel.js b/src/UI/Artist/Index/FooterModel.js
new file mode 100644
index 000000000..235552061
--- /dev/null
+++ b/src/UI/Artist/Index/FooterModel.js
@@ -0,0 +1,4 @@
+var Backbone = require('backbone');
+var _ = require('underscore');
+
+module.exports = Backbone.Model.extend({});
\ No newline at end of file
diff --git a/src/UI/Artist/Index/FooterView.js b/src/UI/Artist/Index/FooterView.js
new file mode 100644
index 000000000..a84d6d01b
--- /dev/null
+++ b/src/UI/Artist/Index/FooterView.js
@@ -0,0 +1,5 @@
+var Marionette = require('marionette');
+
+module.exports = Marionette.CompositeView.extend({
+ template : 'Artist/Index/FooterViewTemplate'
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Index/FooterViewTemplate.hbs b/src/UI/Artist/Index/FooterViewTemplate.hbs
new file mode 100644
index 000000000..58d744386
--- /dev/null
+++ b/src/UI/Artist/Index/FooterViewTemplate.hbs
@@ -0,0 +1,46 @@
+
+
+
+ Continuing (All tracks downloaded)
+ Ended (All tracks downloaded)
+ Missing Tracks (Artist monitored)
+ Missing Tracks (Artist not monitored)
+
+
+
+
+
+
+ Artists
+ {{artist}}
+
+ Ended
+ {{ended}}
+
+ Continuing
+ {{continuing}}
+
+
+
+
+
+ Monitored
+ {{monitored}}
+
+ Unmonitored
+ {{unmonitored}}
+
+
+
+
+
+ Tracks
+ {{episodes}}
+
+ Files
+ {{episodeFiles}}
+
+
+
+
+
diff --git a/src/UI/Artist/Index/Overview/ArtistOverviewCollectionView.js b/src/UI/Artist/Index/Overview/ArtistOverviewCollectionView.js
new file mode 100644
index 000000000..0b879824d
--- /dev/null
+++ b/src/UI/Artist/Index/Overview/ArtistOverviewCollectionView.js
@@ -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'
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Index/Overview/ArtistOverviewCollectionViewTemplate.hbs b/src/UI/Artist/Index/Overview/ArtistOverviewCollectionViewTemplate.hbs
new file mode 100644
index 000000000..04687a280
--- /dev/null
+++ b/src/UI/Artist/Index/Overview/ArtistOverviewCollectionViewTemplate.hbs
@@ -0,0 +1 @@
+
diff --git a/src/UI/Artist/Index/Overview/ArtistOverviewItemView.js b/src/UI/Artist/Index/Overview/ArtistOverviewItemView.js
new file mode 100644
index 000000000..82b9485da
--- /dev/null
+++ b/src/UI/Artist/Index/Overview/ArtistOverviewItemView.js
@@ -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'
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Index/Overview/ArtistOverviewItemViewTemplate.hbs b/src/UI/Artist/Index/Overview/ArtistOverviewItemViewTemplate.hbs
new file mode 100644
index 000000000..222287a2e
--- /dev/null
+++ b/src/UI/Artist/Index/Overview/ArtistOverviewItemViewTemplate.hbs
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+ {{truncate overview 600}}
+
+
+
+
+
+
+
+
+
+
+ {{albumCountHelper}}
+
+ {{profile profileId}}
+
+
+ {{> EpisodeProgressPartial }}
+
+
+ Path {{path}}
+
+
+
+
+
diff --git a/src/UI/Artist/Index/Posters/ArtistPostersCollectionView.js b/src/UI/Artist/Index/Posters/ArtistPostersCollectionView.js
new file mode 100644
index 000000000..cc712d9da
--- /dev/null
+++ b/src/UI/Artist/Index/Posters/ArtistPostersCollectionView.js
@@ -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'
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Index/Posters/ArtistPostersCollectionViewTemplate.hbs b/src/UI/Artist/Index/Posters/ArtistPostersCollectionViewTemplate.hbs
new file mode 100644
index 000000000..0ac6a892e
--- /dev/null
+++ b/src/UI/Artist/Index/Posters/ArtistPostersCollectionViewTemplate.hbs
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/UI/Artist/Index/Posters/ArtistPostersItemView.js b/src/UI/Artist/Index/Posters/ArtistPostersItemView.js
new file mode 100644
index 000000000..3dc34cb4f
--- /dev/null
+++ b/src/UI/Artist/Index/Posters/ArtistPostersItemView.js
@@ -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();
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/Index/Posters/ArtistPostersItemViewTemplate.hbs b/src/UI/Artist/Index/Posters/ArtistPostersItemViewTemplate.hbs
new file mode 100644
index 000000000..62a0a0a10
--- /dev/null
+++ b/src/UI/Artist/Index/Posters/ArtistPostersItemViewTemplate.hbs
@@ -0,0 +1,30 @@
+
+
+
+
+
+ {{> EpisodeProgressPartial }}
+
+ {{#if nextAiring}}
+ {{RelativeDate nextAiring}}
+ {{/if}}
+
+
+
diff --git a/src/UI/Artist/Index/TrackProgressPartial.hbs b/src/UI/Artist/Index/TrackProgressPartial.hbs
new file mode 100644
index 000000000..db5c49a2b
--- /dev/null
+++ b/src/UI/Artist/Index/TrackProgressPartial.hbs
@@ -0,0 +1,4 @@
+
+
{{episodeFileCount}} / {{episodeCount}}
+
{{episodeFileCount}} / {{episodeCount}}
+
\ No newline at end of file
diff --git a/src/UI/Artist/TrackCollection.js b/src/UI/Artist/TrackCollection.js
new file mode 100644
index 000000000..33bd4c3a3
--- /dev/null
+++ b/src/UI/Artist/TrackCollection.js
@@ -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);
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/TrackFileCollection.js b/src/UI/Artist/TrackFileCollection.js
new file mode 100644
index 000000000..19c58ebee
--- /dev/null
+++ b/src/UI/Artist/TrackFileCollection.js
@@ -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);
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/TrackFileModel.js b/src/UI/Artist/TrackFileModel.js
new file mode 100644
index 000000000..3986a5948
--- /dev/null
+++ b/src/UI/Artist/TrackFileModel.js
@@ -0,0 +1,3 @@
+var Backbone = require('backbone');
+
+module.exports = Backbone.Model.extend({});
\ No newline at end of file
diff --git a/src/UI/Artist/TrackModel.js b/src/UI/Artist/TrackModel.js
new file mode 100644
index 000000000..ebb72cf29
--- /dev/null
+++ b/src/UI/Artist/TrackModel.js
@@ -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);
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Artist/artist.less b/src/UI/Artist/artist.less
new file mode 100644
index 000000000..e0f132e30
--- /dev/null
+++ b/src/UI/Artist/artist.less
@@ -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;
+ }
+}
diff --git a/src/UI/Cells/ArtistActionsCell.js b/src/UI/Cells/ArtistActionsCell.js
new file mode 100644
index 000000000..b8332ce1a
--- /dev/null
+++ b/src/UI/Cells/ArtistActionsCell.js
@@ -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(' ' +
+ ' ');
+
+ 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
+ });
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Cells/ArtistFolderCell.js b/src/UI/Cells/ArtistFolderCell.js
new file mode 100644
index 000000000..8e1052fe9
--- /dev/null
+++ b/src/UI/Cells/ArtistFolderCell.js
@@ -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;
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Cells/ArtistStatusCell.js b/src/UI/Cells/ArtistStatusCell.js
new file mode 100644
index 000000000..aa53482f1
--- /dev/null
+++ b/src/UI/Cells/ArtistStatusCell.js
@@ -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(' ');
+ this._setStatusWeight(3);
+ }
+
+ else if (!monitored) {
+ this.$el.html(' ');
+ this._setStatusWeight(2);
+ }
+
+ else {
+ this.$el.html(' ');
+ this._setStatusWeight(1);
+ }
+
+ return this;
+ },
+
+ _setStatusWeight : function(weight) {
+ this.model.set('statusWeight', weight, { silent : true });
+ }
+});
\ No newline at end of file
diff --git a/src/UI/Cells/ArtistTitleCell.js b/src/UI/Cells/ArtistTitleCell.js
new file mode 100644
index 000000000..5dcb62104
--- /dev/null
+++ b/src/UI/Cells/ArtistTitleCell.js
@@ -0,0 +1,6 @@
+var TemplatedCell = require('./TemplatedCell');
+
+module.exports = TemplatedCell.extend({
+ className : 'artist-title-cell',
+ template : 'Cells/ArtistTitleTemplate'
+});
\ No newline at end of file
diff --git a/src/UI/Cells/ArtistTitleTemplate.hbs b/src/UI/Cells/ArtistTitleTemplate.hbs
new file mode 100644
index 000000000..be7c9b91e
--- /dev/null
+++ b/src/UI/Cells/ArtistTitleTemplate.hbs
@@ -0,0 +1 @@
+{{artistName}}
diff --git a/src/UI/Cells/cells.less b/src/UI/Cells/cells.less
index ca71defbd..b2d97881d 100644
--- a/src/UI/Cells/cells.less
+++ b/src/UI/Cells/cells.less
@@ -5,7 +5,7 @@
@import "../Content/mixins";
@import "../Content/variables";
-.series-title-cell {
+.artist-title-cell {
.text-overflow();
max-width: 450px;
@@ -141,7 +141,7 @@ td.episode-status-cell, td.quality-cell, td.history-quality-cell, td.progress-ce
}
}
-.series-actions-cell {
+.artist-actions-cell {
width : 56px;
min-width : 56px;
}
@@ -184,7 +184,7 @@ td.delete-episode-file-cell {
}
}
-.series-status-cell {
+.artist-status-cell {
width: 16px;
}
diff --git a/src/UI/Config.js b/src/UI/Config.js
index 2115d076a..a080f1343 100644
--- a/src/UI/Config.js
+++ b/src/UI/Config.js
@@ -9,8 +9,8 @@ module.exports = {
Keys : {
DefaultProfileId : 'DefaultProfileId',
DefaultRootFolderId : 'DefaultRootFolderId',
- UseSeasonFolder : 'UseSeasonFolder',
- DefaultSeriesType : 'DefaultSeriesType',
+ UseAlbumFolder : 'UseAlbumFolder',
+ DefaultArtistType : 'DefaultArtistType',
MonitorEpisodes : 'MonitorEpisodes',
AdvancedSettings : 'advancedSettings'
},
diff --git a/src/UI/Content/icons.less b/src/UI/Content/icons.less
index 6de60fb04..5898f8f68 100644
--- a/src/UI/Content/icons.less
+++ b/src/UI/Content/icons.less
@@ -311,7 +311,7 @@
.fa-icon-content(@fa-var-bars);
}
-.icon-lidarr-navbar-series {
+.icon-lidarr-navbar-artist {
.fa-icon-content(@fa-var-play);
}
@@ -376,15 +376,15 @@
.fa-icon-content(@fa-var-refresh);
}
-.icon-lidarr-series-ended {
+.icon-lidarr-artist-ended {
.fa-icon-content(@fa-var-stop);
}
-.icon-lidarr-series-continuing {
+.icon-lidarr-artist-continuing {
.fa-icon-content(@fa-var-play);
}
-.icon-lidarr-series-unmonitored {
+.icon-lidarr-artist-unmonitored {
.fa-icon-content(@fa-var-pause);
}
diff --git a/src/UI/Controller.js b/src/UI/Controller.js
index f1e4032ab..04dcce602 100644
--- a/src/UI/Controller.js
+++ b/src/UI/Controller.js
@@ -3,18 +3,20 @@ var AppLayout = require('./AppLayout');
var Marionette = require('marionette');
var ActivityLayout = require('./Activity/ActivityLayout');
var SettingsLayout = require('./Settings/SettingsLayout');
-var AddSeriesLayout = require('./AddSeries/AddSeriesLayout');
+//var AddSeriesLayout = require('./AddSeries/AddSeriesLayout');
+var AddArtistLayout = require('./AddArtist/AddArtistLayout');
var WantedLayout = require('./Wanted/WantedLayout');
var CalendarLayout = require('./Calendar/CalendarLayout');
var ReleaseLayout = require('./Release/ReleaseLayout');
var SystemLayout = require('./System/SystemLayout');
var SeasonPassLayout = require('./SeasonPass/SeasonPassLayout');
-var SeriesEditorLayout = require('./Series/Editor/SeriesEditorLayout');
+//var SeriesEditorLayout = require('./Series/Editor/SeriesEditorLayout');
+var ArtistEditorLayout = require('./Artist/Editor/ArtistEditorLayout');
module.exports = NzbDroneController.extend({
- addSeries : function(action) {
- this.setTitle('Add Series');
- this.showMainRegion(new AddSeriesLayout({ action : action }));
+ addArtist : function(action) {
+ this.setTitle('Add Artist');
+ this.showMainRegion(new AddArtistLayout({ action : action }));
},
calendar : function() {
@@ -52,8 +54,8 @@ module.exports = NzbDroneController.extend({
this.showMainRegion(new SeasonPassLayout());
},
- seriesEditor : function() {
- this.setTitle('Series Editor');
- this.showMainRegion(new SeriesEditorLayout());
+ artistEditor : function() {
+ this.setTitle('Artist Editor');
+ this.showMainRegion(new ArtistEditorLayout());
}
});
\ No newline at end of file
diff --git a/src/UI/Handlebars/Helpers/Artist.js b/src/UI/Handlebars/Helpers/Artist.js
new file mode 100644
index 000000000..a44f24802
--- /dev/null
+++ b/src/UI/Handlebars/Helpers/Artist.js
@@ -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(' '.format(Handlebars.helpers.defaultImg.call(null, poster[0].url, 250)));
+ } else {
+ var url = poster[0].url.replace(/^https?\:/, '');
+ return new Handlebars.SafeString(' '.format(Handlebars.helpers.defaultImg.call(null, url)));
+ }
+ }
+
+ return new Handlebars.SafeString(' '.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.artistSlug;
+});
+
+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('Season {0} '.format(seasonCount));
+ }
+
+ if (seasonCount === 1) {
+ return new Handlebars.SafeString('{0} Season '.format(seasonCount));
+ }
+
+ return new Handlebars.SafeString('{0} Seasons '.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('{0} Albums '.format(albumCount));
+ }
+
+ return new Handlebars.SafeString('{0} Albums '.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} ({1}) '.format(this.title, this.year));
+});*/
diff --git a/src/UI/Handlebars/backbone.marionette.templates.js b/src/UI/Handlebars/backbone.marionette.templates.js
index 82bf4ec62..0b1364284 100644
--- a/src/UI/Handlebars/backbone.marionette.templates.js
+++ b/src/UI/Handlebars/backbone.marionette.templates.js
@@ -4,7 +4,8 @@ require('./Helpers/DateTime');
require('./Helpers/Html');
require('./Helpers/Numbers');
require('./Helpers/Episode');
-require('./Helpers/Series');
+//require('./Helpers/Series');
+require('./Helpers/Artist');
require('./Helpers/Quality');
require('./Helpers/System');
require('./Helpers/EachReverse');
diff --git a/src/UI/Navbar/NavbarLayoutTemplate.hbs b/src/UI/Navbar/NavbarLayoutTemplate.hbs
index b6d365f9e..4fb80fabd 100644
--- a/src/UI/Navbar/NavbarLayoutTemplate.hbs
+++ b/src/UI/Navbar/NavbarLayoutTemplate.hbs
@@ -19,7 +19,7 @@
diff --git a/src/UI/Navbar/Search.js b/src/UI/Navbar/Search.js
index ec1e14ead..8f8bce656 100644
--- a/src/UI/Navbar/Search.js
+++ b/src/UI/Navbar/Search.js
@@ -2,17 +2,18 @@ var _ = require('underscore');
var $ = require('jquery');
var vent = require('vent');
var Backbone = require('backbone');
-var SeriesCollection = require('../Series/SeriesCollection');
+var ArtistCollection = require('../Artist/ArtistCollection');
require('typeahead');
vent.on(vent.Hotkeys.NavbarSearch, function() {
- $('.x-series-search').focus();
+ $('.x-artist-search').focus();
});
var substringMatcher = function() {
+
return function findMatches (q, cb) {
- var matches = _.select(SeriesCollection.toJSON(), function(series) {
- return series.title.toLowerCase().indexOf(q.toLowerCase()) > -1;
+ var matches = _.select(ArtistCollection.toJSON(), function(artist) {
+ return artist.artistName.toLowerCase().indexOf(q.toLowerCase()) > -1;
});
cb(matches);
};
@@ -24,14 +25,14 @@ $.fn.bindSearch = function() {
highlight : true,
minLength : 1
}, {
- name : 'series',
- displayKey : 'title',
+ name : 'artist',
+ displayKey : 'artistName',
source : substringMatcher()
});
- $(this).on('typeahead:selected typeahead:autocompleted', function(e, series) {
+ $(this).on('typeahead:selected typeahead:autocompleted', function(e, artist) {
this.blur();
$(this).val('');
- Backbone.history.navigate('/series/{0}'.format(series.titleSlug), { trigger : true });
+ Backbone.history.navigate('/artist/{0}'.format(artist.artistSlug), { trigger : true });
});
};
\ No newline at end of file
diff --git a/src/UI/Router.js b/src/UI/Router.js
index 91b42a074..ce8db831c 100644
--- a/src/UI/Router.js
+++ b/src/UI/Router.js
@@ -4,8 +4,8 @@ var Controller = require('./Controller');
module.exports = Marionette.AppRouter.extend({
controller : new Controller(),
appRoutes : {
- 'addseries' : 'addSeries',
- 'addseries/:action(/:query)' : 'addSeries',
+ 'addartist' : 'addArtist',
+ 'addartist/:action(/:query)' : 'addArtist',
'calendar' : 'calendar',
'settings' : 'settings',
'settings/:action(/:query)' : 'settings',
@@ -19,7 +19,7 @@ module.exports = Marionette.AppRouter.extend({
'system' : 'system',
'system/:action' : 'system',
'seasonpass' : 'seasonPass',
- 'serieseditor' : 'seriesEditor',
+ 'artisteditor' : 'artistEditor',
':whatever' : 'showNotFound'
}
});
\ No newline at end of file
diff --git a/src/UI/SeasonPass/SeasonPassFooterView.js b/src/UI/SeasonPass/SeasonPassFooterView.js
index 64a2c8916..f7d02706c 100644
--- a/src/UI/SeasonPass/SeasonPassFooterView.js
+++ b/src/UI/SeasonPass/SeasonPassFooterView.js
@@ -2,7 +2,7 @@ var _ = require('underscore');
var $ = require('jquery');
var Marionette = require('marionette');
var vent = require('vent');
-var RootFolders = require('../AddSeries/RootFolders/RootFolderCollection');
+var RootFolders = require('../AddArtist/RootFolders/RootFolderCollection');
module.exports = Marionette.ItemView.extend({
template : 'SeasonPass/SeasonPassFooterViewTemplate',
diff --git a/src/UI/Series/Editor/SeriesEditorFooterView.js b/src/UI/Series/Editor/SeriesEditorFooterView.js
index 6f4f83a6c..e2783bbeb 100644
--- a/src/UI/Series/Editor/SeriesEditorFooterView.js
+++ b/src/UI/Series/Editor/SeriesEditorFooterView.js
@@ -2,8 +2,8 @@ var _ = require('underscore');
var Marionette = require('marionette');
var vent = require('vent');
var Profiles = require('../../Profile/ProfileCollection');
-var RootFolders = require('../../AddSeries/RootFolders/RootFolderCollection');
-var RootFolderLayout = require('../../AddSeries/RootFolders/RootFolderLayout');
+var RootFolders = require('../../AddArtist/RootFolders/RootFolderCollection');
+var RootFolderLayout = require('../../AddArtist/RootFolders/RootFolderLayout');
var UpdateFilesSeriesView = require('./Organize/OrganizeFilesView');
var Config = require('../../Config');
diff --git a/src/UI/Series/Index/EmptyTemplate.hbs b/src/UI/Series/Index/EmptyTemplate.hbs
index 1f31061e3..23b8b513c 100644
--- a/src/UI/Series/Index/EmptyTemplate.hbs
+++ b/src/UI/Series/Index/EmptyTemplate.hbs
@@ -7,7 +7,7 @@
-
+
Add Music
diff --git a/src/UI/Series/Index/SeriesIndexLayout.js b/src/UI/Series/Index/SeriesIndexLayout.js
index a8ae3d61c..09be2418c 100644
--- a/src/UI/Series/Index/SeriesIndexLayout.js
+++ b/src/UI/Series/Index/SeriesIndexLayout.js
@@ -81,9 +81,9 @@ module.exports = Marionette.Layout.extend({
collapse : true,
items : [
{
- title : 'Add Series',
+ title : 'Add Artist',
icon : 'icon-lidarr-add',
- route : 'addseries'
+ route : 'addartist'
},
{
title : 'Season Pass',
diff --git a/src/UI/Shared/Modal/ModalController.js b/src/UI/Shared/Modal/ModalController.js
index ae5c1ec8c..f373f92d9 100644
--- a/src/UI/Shared/Modal/ModalController.js
+++ b/src/UI/Shared/Modal/ModalController.js
@@ -1,8 +1,8 @@
var vent = require('vent');
var AppLayout = require('../../AppLayout');
var Marionette = require('marionette');
-var EditSeriesView = require('../../Series/Edit/EditSeriesView');
-var DeleteSeriesView = require('../../Series/Delete/DeleteSeriesView');
+var EditArtistView = require('../../Artist/Edit/EditArtistView');
+var DeleteArtistView = require('../../Artist/Delete/DeleteArtistView');
var EpisodeDetailsLayout = require('../../Episode/EpisodeDetailsLayout');
var HistoryDetailsLayout = require('../../Activity/History/Details/HistoryDetailsLayout');
var LogDetailsView = require('../../System/Logs/Table/Details/LogDetailsView');
@@ -16,8 +16,8 @@ module.exports = Marionette.AppRouter.extend({
vent.on(vent.Commands.CloseModalCommand, this._closeModal, this);
vent.on(vent.Commands.OpenModal2Command, this._openModal2, this);
vent.on(vent.Commands.CloseModal2Command, this._closeModal2, this);
- vent.on(vent.Commands.EditSeriesCommand, this._editSeries, this);
- vent.on(vent.Commands.DeleteSeriesCommand, this._deleteSeries, this);
+ vent.on(vent.Commands.EditArtistCommand, this._editArtist, this);
+ vent.on(vent.Commands.DeleteArtistCommand, this._deleteArtist, this);
vent.on(vent.Commands.ShowEpisodeDetails, this._showEpisode, this);
vent.on(vent.Commands.ShowHistoryDetails, this._showHistory, this);
vent.on(vent.Commands.ShowLogDetails, this._showLogDetails, this);
@@ -43,13 +43,13 @@ module.exports = Marionette.AppRouter.extend({
AppLayout.modalRegion2.closeModal();
},
- _editSeries : function(options) {
- var view = new EditSeriesView({ model : options.series });
+ _editArtist : function(options) {
+ var view = new EditArtistView({ model : options.artist });
AppLayout.modalRegion.show(view);
},
- _deleteSeries : function(options) {
- var view = new DeleteSeriesView({ model : options.series });
+ _deleteArtist : function(options) {
+ var view = new DeleteArtistView({ model : options.artist });
AppLayout.modalRegion.show(view);
},
diff --git a/src/UI/index.html b/src/UI/index.html
index d5e11456f..0b27f5c33 100644
--- a/src/UI/index.html
+++ b/src/UI/index.html
@@ -21,11 +21,11 @@
-
+
-
+
diff --git a/src/UI/vent.js b/src/UI/vent.js
index 0baf8d0d4..7603be871 100644
--- a/src/UI/vent.js
+++ b/src/UI/vent.js
@@ -13,8 +13,8 @@ vent.Events = {
};
vent.Commands = {
- EditSeriesCommand : 'EditSeriesCommand',
- DeleteSeriesCommand : 'DeleteSeriesCommand',
+ EditArtistCommand : 'EditArtistCommand',
+ DeleteArtistCommand : 'DeleteArtistCommand',
OpenModalCommand : 'OpenModalCommand',
CloseModalCommand : 'CloseModalCommand',
OpenModal2Command : 'OpenModal2Command',