mirror of https://github.com/lidarr/Lidarr
Many UI and API Improvements (#8)
This fixes and implements many items on the ArtistIndex Page and ArtistDetailPage * Create ArtistStatistics Core Module and tie into API. * Create Members Class and tie into ArtistModel and Artist API resource. * Finish Out Album API resources and pass to ArtistDetailPage. * Finish Out Track and TrackFile API resources and pass to ArtistDetailPage. * Lots of UI work on Artist Detail Page to get Albums and Track list working. * Add Cover and Disc Image Types to MediaCover Class * Remove AddSeries UI Flow, since we have replaced with AddArtist (Cleanup)
This commit is contained in:
parent
2c7398ac66
commit
d8ea0a3243
|
@ -16,10 +16,10 @@ gulp.task('less', function() {
|
|||
paths.src.content + 'bootstrap.less',
|
||||
paths.src.content + 'theme.less',
|
||||
paths.src.content + 'overrides.less',
|
||||
paths.src.root + 'Series/series.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 + 'AddSeries/addSeries.less',
|
||||
paths.src.root + 'AddArtist/addArtist.less',
|
||||
paths.src.root + 'Calendar/calendar.less',
|
||||
paths.src.root + 'Cells/cells.less',
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.SignalR;
|
||||
|
||||
namespace NzbDrone.Api.Albums
|
||||
{
|
||||
public class AlbumModule : AlbumModuleWithSignalR
|
||||
{
|
||||
public AlbumModule(IArtistService artistService,
|
||||
IAlbumService albumService,
|
||||
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||
IBroadcastSignalRMessage signalRBroadcaster)
|
||||
: base(albumService, artistService, qualityUpgradableSpecification, signalRBroadcaster)
|
||||
{
|
||||
GetResourceAll = GetAlbums;
|
||||
UpdateResource = SetMonitored;
|
||||
}
|
||||
|
||||
private List<AlbumResource> GetAlbums()
|
||||
{
|
||||
if (!Request.Query.ArtistId.HasValue)
|
||||
{
|
||||
throw new BadRequestException("artistId is missing");
|
||||
}
|
||||
|
||||
var artistId = (int)Request.Query.ArtistId;
|
||||
|
||||
var resources = MapToResource(_albumService.GetAlbumsByArtist(artistId), false);
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
private void SetMonitored(AlbumResource albumResource)
|
||||
{
|
||||
_albumService.SetAlbumMonitored(albumResource.Id, albumResource.Monitored);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Api.TrackFiles;
|
||||
using NzbDrone.Api.Music;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.SignalR;
|
||||
|
||||
namespace NzbDrone.Api.Albums
|
||||
{
|
||||
public abstract class AlbumModuleWithSignalR : NzbDroneRestModuleWithSignalR<AlbumResource, Track>
|
||||
{
|
||||
protected readonly IAlbumService _albumService;
|
||||
protected readonly IArtistService _artistService;
|
||||
protected readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
|
||||
|
||||
protected AlbumModuleWithSignalR(IAlbumService albumService,
|
||||
IArtistService artistService,
|
||||
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||
IBroadcastSignalRMessage signalRBroadcaster)
|
||||
: base(signalRBroadcaster)
|
||||
{
|
||||
_albumService = albumService;
|
||||
_artistService = artistService;
|
||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||
|
||||
GetResourceById = GetAlbum;
|
||||
}
|
||||
|
||||
protected AlbumModuleWithSignalR(IAlbumService albumService,
|
||||
IArtistService artistService,
|
||||
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||
IBroadcastSignalRMessage signalRBroadcaster,
|
||||
string resource)
|
||||
: base(signalRBroadcaster, resource)
|
||||
{
|
||||
_albumService = albumService;
|
||||
_artistService = artistService;
|
||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||
|
||||
GetResourceById = GetAlbum;
|
||||
}
|
||||
|
||||
protected AlbumResource GetAlbum(int id)
|
||||
{
|
||||
var album = _albumService.GetAlbum(id);
|
||||
var resource = MapToResource(album, true);
|
||||
return resource;
|
||||
}
|
||||
|
||||
protected AlbumResource MapToResource(Album album, bool includeArtist)
|
||||
{
|
||||
var resource = album.ToResource();
|
||||
|
||||
if (includeArtist)
|
||||
{
|
||||
var artist = album.Artist ?? _artistService.GetArtist(album.ArtistId);
|
||||
|
||||
if (includeArtist)
|
||||
{
|
||||
resource.Artist = artist.ToResource();
|
||||
}
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
protected List<AlbumResource> MapToResource(List<Album> albums, bool includeArtist)
|
||||
{
|
||||
var result = albums.ToResource();
|
||||
|
||||
if (includeArtist)
|
||||
{
|
||||
var artistDict = new Dictionary<int, Core.Music.Artist>();
|
||||
for (var i = 0; i < albums.Count; i++)
|
||||
{
|
||||
var album = albums[i];
|
||||
var resource = result[i];
|
||||
|
||||
var artist = album.Artist ?? artistDict.GetValueOrDefault(albums[i].ArtistId) ?? _artistService.GetArtist(albums[i].ArtistId);
|
||||
artistDict[artist.Id] = artist;
|
||||
|
||||
if (includeArtist)
|
||||
{
|
||||
resource.Artist = artist.ToResource();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//TODO: Implement Track or Album Grabbed/Dowloaded Events
|
||||
|
||||
//public void Handle(TrackGrabbedEvent message)
|
||||
//{
|
||||
// foreach (var track in message.Track.Tracks)
|
||||
// {
|
||||
// var resource = track.ToResource();
|
||||
// resource.Grabbed = true;
|
||||
|
||||
// BroadcastResourceChange(ModelAction.Updated, resource);
|
||||
// }
|
||||
//}
|
||||
|
||||
//public void Handle(TrackDownloadedEvent message)
|
||||
//{
|
||||
// foreach (var album in message.Album.Albums)
|
||||
// {
|
||||
// BroadcastResourceChange(ModelAction.Updated, album.Id);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
|
@ -3,33 +3,47 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Api.Music;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
|
||||
namespace NzbDrone.Api.Music
|
||||
namespace NzbDrone.Api.Albums
|
||||
{
|
||||
public class AlbumResource
|
||||
public class AlbumResource : RestResource
|
||||
{
|
||||
public string AlbumId { get; set; }
|
||||
public string AlbumName { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
public string Label { get; set; }
|
||||
public bool Monitored { get; set; }
|
||||
public string Path { get; set; }
|
||||
public int ProfileId { get; set; }
|
||||
public Ratings Ratings { get; set; }
|
||||
public DateTime ReleaseDate { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
public string ArtworkUrl { get; set; }
|
||||
public ArtistResource Artist { get; set; }
|
||||
public List<MediaCover> Images { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public static class AlbumResourceMapper
|
||||
{
|
||||
public static AlbumResource ToResource(this Album model)
|
||||
public static AlbumResource ToResource(this Core.Music.Album model)
|
||||
{
|
||||
if (model == null) return null;
|
||||
|
||||
return new AlbumResource
|
||||
{
|
||||
AlbumId = model.ForeignAlbumId,
|
||||
Id = model.Id,
|
||||
Label = model.Label,
|
||||
Path = model.Path,
|
||||
ProfileId = model.ProfileId,
|
||||
Monitored = model.Monitored,
|
||||
ReleaseDate = model.ReleaseDate,
|
||||
AlbumName = model.Title,
|
||||
Genres = model.Genres,
|
||||
Title = model.Title,
|
||||
Images = model.Images,
|
||||
Ratings = model.Ratings,
|
||||
//Genres = model.Genres,
|
||||
//ArtworkUrl = model.ArtworkUrl
|
||||
};
|
||||
}
|
||||
|
@ -38,13 +52,19 @@ namespace NzbDrone.Api.Music
|
|||
{
|
||||
if (resource == null) return null;
|
||||
|
||||
return new Album
|
||||
return new Core.Music.Album
|
||||
{
|
||||
ForeignAlbumId = resource.AlbumId,
|
||||
Id = resource.Id,
|
||||
Label = resource.Label,
|
||||
Path = resource.Path,
|
||||
Monitored = resource.Monitored,
|
||||
ProfileId = resource.ProfileId,
|
||||
ReleaseDate = resource.ReleaseDate,
|
||||
Title = resource.AlbumName,
|
||||
Genres = resource.Genres,
|
||||
Title = resource.Title,
|
||||
Images = resource.Images,
|
||||
Ratings = resource.Ratings,
|
||||
//Genres = resource.Genres,
|
||||
//ArtworkUrl = resource.ArtworkUrl
|
||||
};
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
using FluentValidation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
|
@ -7,12 +10,10 @@ using NzbDrone.Core.MediaFiles.Events;
|
|||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Music.Events;
|
||||
using NzbDrone.Core.SeriesStats;
|
||||
using NzbDrone.Core.ArtistStats;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
using NzbDrone.SignalR;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Api.Music
|
||||
{
|
||||
|
@ -26,14 +27,14 @@ namespace NzbDrone.Api.Music
|
|||
//IHandle<MediaCoversUpdatedEvent>
|
||||
{
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IAddArtistService _addSeriesService;
|
||||
private readonly ISeriesStatisticsService _seriesStatisticsService;
|
||||
private readonly IAddArtistService _addArtistService;
|
||||
private readonly IArtistStatisticsService _artistStatisticsService;
|
||||
private readonly IMapCoversToLocal _coverMapper;
|
||||
|
||||
public ArtistModule(IBroadcastSignalRMessage signalRBroadcaster,
|
||||
IArtistService artistService,
|
||||
IAddArtistService addSeriesService,
|
||||
ISeriesStatisticsService seriesStatisticsService,
|
||||
IAddArtistService addArtistService,
|
||||
IArtistStatisticsService artistStatisticsService,
|
||||
IMapCoversToLocal coverMapper,
|
||||
RootFolderValidator rootFolderValidator,
|
||||
ArtistPathValidator seriesPathValidator,
|
||||
|
@ -45,8 +46,8 @@ namespace NzbDrone.Api.Music
|
|||
: base(signalRBroadcaster)
|
||||
{
|
||||
_artistService = artistService;
|
||||
_addSeriesService = addSeriesService;
|
||||
_seriesStatisticsService = seriesStatisticsService;
|
||||
_addArtistService = addArtistService;
|
||||
_artistStatisticsService = artistStatisticsService;
|
||||
|
||||
_coverMapper = coverMapper;
|
||||
|
||||
|
@ -88,7 +89,7 @@ namespace NzbDrone.Api.Music
|
|||
|
||||
var resource = artist.ToResource();
|
||||
MapCoversToLocal(resource);
|
||||
//FetchAndLinkSeriesStatistics(resource);
|
||||
FetchAndLinkArtistStatistics(resource);
|
||||
//PopulateAlternateTitles(resource);
|
||||
|
||||
return resource;
|
||||
|
@ -96,10 +97,11 @@ namespace NzbDrone.Api.Music
|
|||
|
||||
private List<ArtistResource> AllArtist()
|
||||
{
|
||||
//var seriesStats = _seriesStatisticsService.SeriesStatistics();
|
||||
var artistStats = _artistStatisticsService.ArtistStatistics();
|
||||
var artistResources = _artistService.GetAllArtists().ToResource();
|
||||
|
||||
MapCoversToLocal(artistResources.ToArray());
|
||||
//LinkSeriesStatistics(seriesResources, seriesStats);
|
||||
LinkArtistStatistics(artistResources, artistStats);
|
||||
//PopulateAlternateTitles(seriesResources);
|
||||
|
||||
return artistResources;
|
||||
|
@ -109,7 +111,7 @@ namespace NzbDrone.Api.Music
|
|||
{
|
||||
var model = artistResource.ToModel();
|
||||
|
||||
return _addSeriesService.AddArtist(model).Id;
|
||||
return _addArtistService.AddArtist(model).Id;
|
||||
}
|
||||
|
||||
private void UpdatArtist(ArtistResource artistResource)
|
||||
|
@ -142,6 +144,44 @@ namespace NzbDrone.Api.Music
|
|||
}
|
||||
}
|
||||
|
||||
private void FetchAndLinkArtistStatistics(ArtistResource resource)
|
||||
{
|
||||
LinkArtistStatistics(resource, _artistStatisticsService.ArtistStatistics(resource.Id));
|
||||
}
|
||||
|
||||
|
||||
private void LinkArtistStatistics(List<ArtistResource> resources, List<ArtistStatistics> artistStatistics)
|
||||
{
|
||||
var dictArtistStats = artistStatistics.ToDictionary(v => v.ArtistId);
|
||||
|
||||
foreach (var artist in resources)
|
||||
{
|
||||
var stats = dictArtistStats.GetValueOrDefault(artist.Id);
|
||||
if (stats == null) continue;
|
||||
|
||||
LinkArtistStatistics(artist, stats);
|
||||
}
|
||||
}
|
||||
|
||||
private void LinkArtistStatistics(ArtistResource resource, ArtistStatistics artistStatistics)
|
||||
{
|
||||
resource.TotalTrackCount = artistStatistics.TotalTrackCount;
|
||||
resource.TrackCount = artistStatistics.TrackCount;
|
||||
resource.TrackFileCount = artistStatistics.TrackFileCount;
|
||||
resource.SizeOnDisk = artistStatistics.SizeOnDisk;
|
||||
resource.AlbumCount = artistStatistics.AlbumCount;
|
||||
|
||||
//if (artistStatistics.AlbumStatistics != null)
|
||||
//{
|
||||
// var dictSeasonStats = artistStatistics.SeasonStatistics.ToDictionary(v => v.SeasonNumber);
|
||||
|
||||
// foreach (var album in resource.Albums)
|
||||
// {
|
||||
// album.Statistics = dictSeasonStats.GetValueOrDefault(album.Id).ToResource();
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
public void Handle(TrackImportedEvent message)
|
||||
{
|
||||
BroadcastResourceChange(ModelAction.Updated, message.ImportedTrack.Id); // TODO: Ensure we can pass DB ID instead of Metadata ID (SpotifyID)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Api.Series;
|
||||
using NzbDrone.Api.Albums;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Music;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -23,19 +24,10 @@ namespace NzbDrone.Api.Music
|
|||
public string MBId { get; set; }
|
||||
public int TADBId { get; set; }
|
||||
public int DiscogsId { get; set; }
|
||||
public string AMId { get; set; }
|
||||
public string AllMusicId { get; set; }
|
||||
public string Overview { get; set; }
|
||||
|
||||
public int AlbumCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Albums == null) return 0;
|
||||
|
||||
return Albums.Where(s => s.AlbumId != "").Count(); // TODO: CHeck this condition
|
||||
}
|
||||
}
|
||||
|
||||
public int? AlbumCount{ get; set; }
|
||||
public int? TotalTrackCount { get; set; }
|
||||
public int? TrackCount { get; set; }
|
||||
public int? TrackFileCount { get; set; }
|
||||
|
@ -43,6 +35,7 @@ namespace NzbDrone.Api.Music
|
|||
//public SeriesStatusType Status { get; set; }
|
||||
|
||||
public List<MediaCover> Images { get; set; }
|
||||
public List<Member> Members { get; set; }
|
||||
|
||||
public string RemotePoster { get; set; }
|
||||
public List<AlbumResource> Albums { get; set; }
|
||||
|
@ -61,7 +54,7 @@ namespace NzbDrone.Api.Music
|
|||
public List<string> Genres { get; set; }
|
||||
public HashSet<int> Tags { get; set; }
|
||||
public DateTime Added { get; set; }
|
||||
public AddSeriesOptions AddOptions { get; set; }
|
||||
public AddArtistOptions AddOptions { get; set; }
|
||||
public Ratings Ratings { get; set; }
|
||||
public string NameSlug { get; set; }
|
||||
}
|
||||
|
@ -78,14 +71,14 @@ namespace NzbDrone.Api.Music
|
|||
MBId = model.MBId,
|
||||
TADBId = model.TADBId,
|
||||
DiscogsId = model.DiscogsId,
|
||||
AMId = model.AMId,
|
||||
AllMusicId = model.AMId,
|
||||
Name = model.Name,
|
||||
//AlternateTitles
|
||||
//SortTitle = resource.SortTitle,
|
||||
|
||||
//TotalEpisodeCount
|
||||
//EpisodeCount
|
||||
//EpisodeFileCount
|
||||
//TotalTrackCount
|
||||
//TrackCount
|
||||
//TrackFileCount
|
||||
//SizeOnDisk
|
||||
//Status = resource.Status,
|
||||
Overview = model.Overview,
|
||||
|
@ -94,6 +87,7 @@ namespace NzbDrone.Api.Music
|
|||
//Network = resource.Network,
|
||||
//AirTime = resource.AirTime,
|
||||
Images = model.Images,
|
||||
Members = model.Members,
|
||||
|
||||
Albums = model.Albums.ToResource(),
|
||||
//Year = resource.Year,
|
||||
|
@ -119,7 +113,7 @@ namespace NzbDrone.Api.Music
|
|||
Tags = model.Tags,
|
||||
Added = model.Added,
|
||||
AddOptions = model.AddOptions,
|
||||
//Ratings = resource.Ratings
|
||||
Ratings = model.Ratings,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -137,10 +131,10 @@ namespace NzbDrone.Api.Music
|
|||
MBId = resource.MBId,
|
||||
TADBId = resource.TADBId,
|
||||
DiscogsId = resource.DiscogsId,
|
||||
AMId = resource.AMId,
|
||||
AMId = resource.AllMusicId, //TODO change model and DB to AllMusic instead of AM
|
||||
//TotalEpisodeCount
|
||||
//EpisodeCount
|
||||
//EpisodeFileCount
|
||||
//TrackCount
|
||||
//TrackFileCount
|
||||
//SizeOnDisk
|
||||
//Status = resource.Status,
|
||||
Overview = resource.Overview,
|
||||
|
@ -149,6 +143,7 @@ namespace NzbDrone.Api.Music
|
|||
//Network = resource.Network,
|
||||
//AirTime = resource.AirTime,
|
||||
Images = resource.Images,
|
||||
Members = resource.Members,
|
||||
|
||||
Albums = resource.Albums.ToModel(),
|
||||
//Year = resource.Year,
|
||||
|
@ -166,7 +161,7 @@ namespace NzbDrone.Api.Music
|
|||
Tags = resource.Tags,
|
||||
Added = resource.Added,
|
||||
AddOptions = resource.AddOptions,
|
||||
//Ratings = resource.Ratings
|
||||
Ratings = resource.Ratings
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
<Compile Include="..\NzbDrone.Common\Properties\SharedAssemblyInfo.cs">
|
||||
<Link>Properties\SharedAssemblyInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Albums\AlbumModuleWithSignalR.cs" />
|
||||
<Compile Include="Authentication\AuthenticationService.cs" />
|
||||
<Compile Include="Authentication\EnableAuthInNancy.cs" />
|
||||
<Compile Include="Authentication\AuthenticationModule.cs" />
|
||||
|
@ -106,6 +107,7 @@
|
|||
<Compile Include="Commands\CommandResource.cs" />
|
||||
<Compile Include="TrackFiles\TrackFileModule.cs" />
|
||||
<Compile Include="TrackFiles\TrackFileResource.cs" />
|
||||
<Compile Include="Albums\AlbumModule.cs" />
|
||||
<Compile Include="Tracks\TrackModule.cs" />
|
||||
<Compile Include="Tracks\TrackModuleWithSignalR.cs" />
|
||||
<Compile Include="Tracks\TrackResource.cs" />
|
||||
|
@ -118,7 +120,7 @@
|
|||
<Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" />
|
||||
<Compile Include="Indexers\ReleaseModuleBase.cs" />
|
||||
<Compile Include="Indexers\ReleasePushModule.cs" />
|
||||
<Compile Include="Music\AlbumResource.cs" />
|
||||
<Compile Include="Albums\AlbumResource.cs" />
|
||||
<Compile Include="Music\ArtistLookupModule.cs" />
|
||||
<Compile Include="Music\ArtistModule.cs" />
|
||||
<Compile Include="Music\ArtistResource.cs" />
|
||||
|
|
|
@ -14,7 +14,8 @@ namespace NzbDrone.Api.Tracks
|
|||
public int ArtistId { get; set; }
|
||||
public int TrackFileId { get; set; }
|
||||
public int AlbumId { get; set; }
|
||||
//public int EpisodeNumber { get; set; }
|
||||
public bool Explicit { get; set; }
|
||||
public int TrackNumber { get; set; }
|
||||
public string Title { get; set; }
|
||||
//public string AirDate { get; set; }
|
||||
//public DateTime? AirDateUtc { get; set; }
|
||||
|
@ -30,6 +31,7 @@ namespace NzbDrone.Api.Tracks
|
|||
//public bool UnverifiedSceneNumbering { get; set; }
|
||||
//public string SeriesTitle { get; set; }
|
||||
public ArtistResource Artist { get; set; }
|
||||
public Ratings Ratings { get; set; }
|
||||
|
||||
//Hiding this so people don't think its usable (only used to set the initial state)
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
|
@ -49,7 +51,8 @@ namespace NzbDrone.Api.Tracks
|
|||
ArtistId = model.ArtistId,
|
||||
TrackFileId = model.TrackFileId,
|
||||
AlbumId = model.AlbumId,
|
||||
//EpisodeNumber = model.EpisodeNumber,
|
||||
Explicit = model.Explicit,
|
||||
TrackNumber = model.TrackNumber,
|
||||
Title = model.Title,
|
||||
//AirDate = model.AirDate,
|
||||
//AirDateUtc = model.AirDateUtc,
|
||||
|
@ -58,6 +61,7 @@ namespace NzbDrone.Api.Tracks
|
|||
|
||||
HasFile = model.HasFile,
|
||||
Monitored = model.Monitored,
|
||||
Ratings = model.Ratings,
|
||||
//AbsoluteEpisodeNumber = model.AbsoluteEpisodeNumber,
|
||||
//SceneAbsoluteEpisodeNumber = model.SceneAbsoluteEpisodeNumber,
|
||||
//SceneEpisodeNumber = model.SceneEpisodeNumber,
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.ArtistStats
|
||||
{
|
||||
public class AlbumStatistics : ResultSet
|
||||
{
|
||||
public int ArtistId { get; set; }
|
||||
public int AlbumId { get; set; }
|
||||
public int TrackFileCount { get; set; }
|
||||
public int TrackCount { get; set; }
|
||||
public int TotalTrackCount { get; set; }
|
||||
public long SizeOnDisk { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.ArtistStats
|
||||
{
|
||||
public class ArtistStatistics : ResultSet
|
||||
{
|
||||
public int ArtistId { get; set; }
|
||||
public int AlbumCount { get; set; }
|
||||
public int TrackFileCount { get; set; }
|
||||
public int TrackCount { get; set; }
|
||||
public int TotalTrackCount { get; set; }
|
||||
public long SizeOnDisk { get; set; }
|
||||
public List<AlbumStatistics> AlbumStatistics { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.ArtistStats
|
||||
{
|
||||
public interface IArtistStatisticsRepository
|
||||
{
|
||||
List<AlbumStatistics> ArtistStatistics();
|
||||
List<AlbumStatistics> ArtistStatistics(int artistId);
|
||||
}
|
||||
|
||||
public class ArtistStatisticsRepository : IArtistStatisticsRepository
|
||||
{
|
||||
private readonly IMainDatabase _database;
|
||||
|
||||
public ArtistStatisticsRepository(IMainDatabase database)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
public List<AlbumStatistics> ArtistStatistics()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.AddParameter("currentDate", DateTime.UtcNow);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(GetSelectClause());
|
||||
sb.AppendLine(GetTrackFilesJoin());
|
||||
sb.AppendLine(GetGroupByClause());
|
||||
var queryText = sb.ToString();
|
||||
|
||||
return mapper.Query<AlbumStatistics>(queryText);
|
||||
}
|
||||
|
||||
public List<AlbumStatistics> ArtistStatistics(int artistId)
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.AddParameter("currentDate", DateTime.UtcNow);
|
||||
mapper.AddParameter("artistId", artistId);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(GetSelectClause());
|
||||
sb.AppendLine(GetTrackFilesJoin());
|
||||
sb.AppendLine("WHERE Tracks.ArtistId = @artistId");
|
||||
sb.AppendLine(GetGroupByClause());
|
||||
var queryText = sb.ToString();
|
||||
|
||||
return mapper.Query<AlbumStatistics>(queryText);
|
||||
}
|
||||
|
||||
private string GetSelectClause()
|
||||
{
|
||||
return @"SELECT Tracks.*, SUM(TrackFiles.Size) as SizeOnDisk FROM
|
||||
(SELECT
|
||||
Tracks.ArtistId,
|
||||
Tracks.AlbumId,
|
||||
SUM(CASE WHEN TrackFileId > 0 THEN 1 ELSE 0 END) AS TotalTrackCount,
|
||||
SUM(CASE WHEN Monitored = 1 OR TrackFileId > 0 THEN 1 ELSE 0 END) AS TrackCount,
|
||||
SUM(CASE WHEN TrackFileId > 0 THEN 1 ELSE 0 END) AS TrackFileCount
|
||||
FROM Tracks
|
||||
GROUP BY Tracks.ArtistId, Tracks.AlbumId) as Tracks";
|
||||
}
|
||||
|
||||
private string GetGroupByClause()
|
||||
{
|
||||
return "GROUP BY Tracks.ArtistId, Tracks.AlbumId";
|
||||
}
|
||||
|
||||
private string GetTrackFilesJoin()
|
||||
{
|
||||
return @"LEFT OUTER JOIN TrackFiles
|
||||
ON TrackFiles.ArtistId = Tracks.ArtistId
|
||||
AND TrackFiles.AlbumId = Tracks.AlbumId";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace NzbDrone.Core.ArtistStats
|
||||
{
|
||||
public interface IArtistStatisticsService
|
||||
{
|
||||
List<ArtistStatistics> ArtistStatistics();
|
||||
ArtistStatistics ArtistStatistics(int artistId);
|
||||
}
|
||||
|
||||
public class ArtistStatisticsService : IArtistStatisticsService
|
||||
{
|
||||
private readonly IArtistStatisticsRepository _artistStatisticsRepository;
|
||||
|
||||
public ArtistStatisticsService(IArtistStatisticsRepository artistStatisticsRepository)
|
||||
{
|
||||
_artistStatisticsRepository = artistStatisticsRepository;
|
||||
}
|
||||
|
||||
public List<ArtistStatistics> ArtistStatistics()
|
||||
{
|
||||
var albumStatistics = _artistStatisticsRepository.ArtistStatistics();
|
||||
|
||||
return albumStatistics.GroupBy(s => s.ArtistId).Select(s => MapArtistStatistics(s.ToList())).ToList();
|
||||
}
|
||||
|
||||
public ArtistStatistics ArtistStatistics(int artistId)
|
||||
{
|
||||
var stats = _artistStatisticsRepository.ArtistStatistics(artistId);
|
||||
|
||||
if (stats == null || stats.Count == 0) return new ArtistStatistics();
|
||||
|
||||
return MapArtistStatistics(stats);
|
||||
}
|
||||
|
||||
private ArtistStatistics MapArtistStatistics(List<AlbumStatistics> albumStatistics)
|
||||
{
|
||||
var artistStatistics = new ArtistStatistics
|
||||
{
|
||||
AlbumStatistics = albumStatistics,
|
||||
AlbumCount = albumStatistics.Count,
|
||||
ArtistId = albumStatistics.First().ArtistId,
|
||||
TrackFileCount = albumStatistics.Sum(s => s.TrackFileCount),
|
||||
TrackCount = albumStatistics.Sum(s => s.TrackCount),
|
||||
TotalTrackCount = albumStatistics.Sum(s => s.TotalTrackCount),
|
||||
SizeOnDisk = albumStatistics.Sum(s => s.SizeOnDisk)
|
||||
};
|
||||
|
||||
return artistStatistics;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ using NzbDrone.Core.Profiles;
|
|||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Restrictions;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.ArtistStats;
|
||||
using NzbDrone.Core.SeriesStats;
|
||||
using NzbDrone.Core.Tags;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
@ -121,7 +122,7 @@ namespace NzbDrone.Core.Datastore
|
|||
Mapper.Entity<Profile>().RegisterModel("Profiles");
|
||||
Mapper.Entity<Log>().RegisterModel("Logs");
|
||||
Mapper.Entity<NamingConfig>().RegisterModel("NamingConfig");
|
||||
Mapper.Entity<SeasonStatistics>().MapResultSet();
|
||||
Mapper.Entity<AlbumStatistics>().MapResultSet();
|
||||
Mapper.Entity<Blacklist>().RegisterModel("Blacklist");
|
||||
Mapper.Entity<MetadataFile>().RegisterModel("MetadataFiles");
|
||||
Mapper.Entity<SubtitleFile>().RegisterModel("SubtitleFiles");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.MediaCover
|
||||
{
|
||||
|
@ -10,7 +10,9 @@ namespace NzbDrone.Core.MediaCover
|
|||
Banner = 2,
|
||||
Fanart = 3,
|
||||
Screenshot = 4,
|
||||
Headshot = 5
|
||||
Headshot = 5,
|
||||
Cover = 6,
|
||||
Disc = 7
|
||||
}
|
||||
|
||||
public class MediaCover : IEmbeddedDocument
|
||||
|
|
|
@ -365,7 +365,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
|
||||
series.TitleSlug = show.Slug;
|
||||
series.Status = MapSeriesStatus(show.Status);
|
||||
series.Ratings = MapRatings(show.Rating);
|
||||
//series.Ratings = MapRatings(show.Rating);
|
||||
series.Genres = show.Genres;
|
||||
|
||||
if (show.ContentRating.IsNotNullOrWhiteSpace())
|
||||
|
@ -412,7 +412,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
episode.AirDate = oracleEpisode.AirDate;
|
||||
episode.AirDateUtc = oracleEpisode.AirDateUtc;
|
||||
|
||||
episode.Ratings = MapRatings(oracleEpisode.Rating);
|
||||
//episode.Ratings = MapRatings(oracleEpisode.Rating);
|
||||
|
||||
//Don't include series fanart images as episode screenshot
|
||||
if (oracleEpisode.Image != null)
|
||||
|
@ -443,14 +443,14 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
return SeriesStatusType.Continuing;
|
||||
}
|
||||
|
||||
private static Ratings MapRatings(RatingResource rating)
|
||||
private static Core.Music.Ratings MapRatings(RatingResource rating)
|
||||
{
|
||||
if (rating == null)
|
||||
{
|
||||
return new Ratings();
|
||||
return new Core.Music.Ratings();
|
||||
}
|
||||
|
||||
return new Ratings
|
||||
return new Core.Music.Ratings
|
||||
{
|
||||
Votes = rating.Count,
|
||||
Value = rating.Value
|
||||
|
|
|
@ -17,21 +17,25 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
public string ForeignAlbumId { get; set; }
|
||||
public int ArtistId { get; set; }
|
||||
public string Title { get; set; } // NOTE: This should be CollectionName in API
|
||||
public string Title { get; set; }
|
||||
public string CleanTitle { get; set; }
|
||||
public DateTime ReleaseDate { get; set; }
|
||||
public string Label { get; set; }
|
||||
//public int TrackCount { get; set; }
|
||||
public string Path { get; set; }
|
||||
public int ProfileId { get; set; }
|
||||
public List<Track> Tracks { get; set; }
|
||||
//public int DiscCount { get; set; }
|
||||
public bool Monitored { get; set; }
|
||||
public List<MediaCover.MediaCover> Images { get; set; }
|
||||
//public List<Actor> Actors { get; set; } // These are band members. TODO: Refactor
|
||||
//public List<Actor> Actors { get; set; } // TODO: These are band members. TODO: Refactor
|
||||
public List<string> Genres { get; set; }
|
||||
public String AlbumType { get; set; } //Turn this into a type similar to Series Type in TV
|
||||
public String AlbumType { get; set; } // TODO: Turn this into a type similar to Series Type in TV
|
||||
//public string ArtworkUrl { get; set; }
|
||||
//public string Explicitness { get; set; }
|
||||
public AddSeriesOptions AddOptions { get; set; }
|
||||
public Artist Artist { get; set; }
|
||||
public Ratings Ratings { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace NzbDrone.Core.Music
|
|||
List<Album> GetAlbums(int artistId);
|
||||
Album FindByName(string cleanTitle);
|
||||
Album FindById(string spotifyId);
|
||||
void SetMonitoredFlat(Album album, bool monitored);
|
||||
}
|
||||
|
||||
public class AlbumRepository : BasicRepository<Album>, IAlbumRepository
|
||||
|
@ -35,6 +36,12 @@ namespace NzbDrone.Core.Music
|
|||
return Query.Where(s => s.ForeignAlbumId == foreignAlbumId).SingleOrDefault();
|
||||
}
|
||||
|
||||
public void SetMonitoredFlat(Album album, bool monitored)
|
||||
{
|
||||
album.Monitored = monitored;
|
||||
SetFields(album, p => p.Monitored);
|
||||
}
|
||||
|
||||
public Album FindByName(string cleanTitle)
|
||||
{
|
||||
cleanTitle = cleanTitle.ToLowerInvariant();
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace NzbDrone.Core.Music
|
|||
List<Album> GetAllAlbums();
|
||||
Album UpdateAlbum(Album album);
|
||||
List<Album> UpdateAlbums(List<Album> album);
|
||||
void SetAlbumMonitored(int albumId, bool monitored);
|
||||
void InsertMany(List<Album> albums);
|
||||
void UpdateMany(List<Album> albums);
|
||||
void DeleteMany(List<Album> albums);
|
||||
|
@ -134,6 +135,14 @@ namespace NzbDrone.Core.Music
|
|||
return updatedAlbum;
|
||||
}
|
||||
|
||||
public void SetAlbumMonitored(int albumId, bool monitored)
|
||||
{
|
||||
var album = _albumRepository.Get(albumId);
|
||||
_albumRepository.SetMonitoredFlat(album, monitored);
|
||||
|
||||
_logger.Debug("Monitored flag for Album:{0} was set to {1}", albumId, monitored);
|
||||
}
|
||||
|
||||
public List<Album> UpdateAlbums(List<Album> album)
|
||||
{
|
||||
_logger.Debug("Updating {0} album", album.Count);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Profiles;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Music;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -16,7 +16,7 @@ namespace NzbDrone.Core.Music
|
|||
{
|
||||
Images = new List<MediaCover.MediaCover>();
|
||||
Genres = new List<string>();
|
||||
//Members = new List<Person>(); // Artist Band Member? (NOTE: This should be per album)
|
||||
Members = new List<Member>();
|
||||
Albums = new List<Album>();
|
||||
Tags = new HashSet<int>();
|
||||
|
||||
|
@ -45,7 +45,9 @@ namespace NzbDrone.Core.Music
|
|||
public int ProfileId { get; set; }
|
||||
public List<Album> Albums { get; set; }
|
||||
public HashSet<int> Tags { get; set; }
|
||||
public AddSeriesOptions AddOptions { get; set; }
|
||||
public AddArtistOptions AddOptions { get; set; }
|
||||
public Ratings Ratings { get; set; }
|
||||
public List<Member> Members { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
@ -76,6 +78,8 @@ namespace NzbDrone.Core.Music
|
|||
Albums = otherArtist.Albums;
|
||||
Tags = otherArtist.Tags;
|
||||
AddOptions = otherArtist.AddOptions;
|
||||
Ratings = otherArtist.Ratings;
|
||||
Members = otherArtist.Members;
|
||||
|
||||
Albums = otherArtist.Albums;
|
||||
Path = otherArtist.Path;
|
||||
|
@ -85,6 +89,7 @@ namespace NzbDrone.Core.Music
|
|||
RootFolderPath = otherArtist.RootFolderPath;
|
||||
Tags = otherArtist.Tags;
|
||||
AddOptions = otherArtist.AddOptions;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
public class Member : IEmbeddedDocument
|
||||
{
|
||||
public Member()
|
||||
{
|
||||
Images = new List<MediaCover.MediaCover>();
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Instrument { get; set; }
|
||||
public List<MediaCover.MediaCover> Images { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
public class Ratings : IEmbeddedDocument
|
||||
{
|
||||
public int Votes { get; set; }
|
||||
public decimal Value { get; set; }
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ namespace NzbDrone.Core.Music
|
|||
public bool Explicit { get; set; }
|
||||
public bool Monitored { get; set; }
|
||||
public int TrackFileId { get; set; }
|
||||
public Ratings Ratings { get; set; }
|
||||
//public DateTime? ReleaseDate { get; set; }
|
||||
|
||||
public LazyLoaded<TrackFile> TrackFile { get; set; }
|
||||
|
|
|
@ -859,6 +859,7 @@
|
|||
<Compile Include="Extras\Metadata\MetadataType.cs" />
|
||||
<Compile Include="MetadataSource\IProvideSeriesInfo.cs" />
|
||||
<Compile Include="MetadataSource\ISearchForNewSeries.cs" />
|
||||
<Compile Include="Music\Member.cs" />
|
||||
<Compile Include="Music\AddArtistOptions.cs" />
|
||||
<Compile Include="Music\AddArtistService.cs" />
|
||||
<Compile Include="Music\AddArtistValidator.cs" />
|
||||
|
@ -882,6 +883,7 @@
|
|||
<Compile Include="Music\Events\ArtistUpdatedEvent.cs" />
|
||||
<Compile Include="Music\Events\AlbumInfoRefreshedEvent.cs" />
|
||||
<Compile Include="Music\Events\TrackInfoRefreshedEvent.cs" />
|
||||
<Compile Include="Music\Ratings.cs" />
|
||||
<Compile Include="Music\RefreshArtistService.cs" />
|
||||
<Compile Include="Music\RefreshAlbumService.cs" />
|
||||
<Compile Include="Music\RefreshTrackService.cs" />
|
||||
|
@ -1104,6 +1106,10 @@
|
|||
</Compile>
|
||||
<Compile Include="RootFolders\UnmappedFolder.cs" />
|
||||
<Compile Include="Security.cs" />
|
||||
<Compile Include="ArtistStats\AlbumStatistics.cs" />
|
||||
<Compile Include="ArtistStats\ArtistStatistics.cs" />
|
||||
<Compile Include="ArtistStats\ArtistStatisticsRepository.cs" />
|
||||
<Compile Include="ArtistStats\ArtistStatisticsService.cs" />
|
||||
<Compile Include="SeriesStats\SeasonStatistics.cs" />
|
||||
<Compile Include="SeriesStats\SeriesStatistics.cs" />
|
||||
<Compile Include="SeriesStats\SeriesStatisticsRepository.cs" />
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
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;
|
||||
}
|
||||
});
|
|
@ -1,53 +0,0 @@
|
|||
var vent = require('vent');
|
||||
var AppLayout = require('../AppLayout');
|
||||
var Marionette = require('marionette');
|
||||
var RootFolderLayout = require('./RootFolders/RootFolderLayout');
|
||||
var ExistingSeriesCollectionView = require('./Existing/AddExistingSeriesCollectionView');
|
||||
var AddSeriesView = require('./AddSeriesView');
|
||||
var ProfileCollection = require('../Profile/ProfileCollection');
|
||||
var RootFolderCollection = require('./RootFolders/RootFolderCollection');
|
||||
require('../Artist/ArtistCollection');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'AddSeries/AddSeriesLayoutTemplate',
|
||||
|
||||
regions : {
|
||||
workspace : '#add-series-workspace'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-import' : '_importSeries',
|
||||
'click .x-add-new' : '_addSeries'
|
||||
},
|
||||
|
||||
attributes : {
|
||||
id : 'add-series-screen'
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
ProfileCollection.fetch();
|
||||
RootFolderCollection.fetch().done(function() {
|
||||
RootFolderCollection.synced = true;
|
||||
});
|
||||
},
|
||||
|
||||
onShow : function() {
|
||||
this.workspace.show(new AddSeriesView());
|
||||
},
|
||||
|
||||
_folderSelected : function(options) {
|
||||
vent.trigger(vent.Commands.CloseModalCommand);
|
||||
|
||||
this.workspace.show(new ExistingSeriesCollectionView({ model : options.model }));
|
||||
},
|
||||
|
||||
_importSeries : function() {
|
||||
this.rootFolderLayout = new RootFolderLayout();
|
||||
this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected);
|
||||
AppLayout.modalRegion.show(this.rootFolderLayout);
|
||||
},
|
||||
|
||||
_addSeries : function() {
|
||||
this.workspace.show(new AddSeriesView());
|
||||
}
|
||||
});
|
|
@ -1,17 +0,0 @@
|
|||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="btn-group add-series-btn-group btn-group-lg btn-block">
|
||||
<button type="button" class="btn btn-default col-md-10 col-xs-8 add-series-import-btn x-import">
|
||||
<i class="icon-lidarr-hdd"/>
|
||||
Import existing artists on disk
|
||||
</button>
|
||||
<button class="btn btn-default col-md-2 col-xs-4 x-add-new"><i class="icon-lidarr-active hidden-xs"></i> Add New Artist</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="add-series-workspace"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
var _ = require('underscore');
|
||||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
var AddSeriesCollection = require('./AddSeriesCollection');
|
||||
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 : 'AddSeries/AddSeriesViewTemplate',
|
||||
|
||||
regions : {
|
||||
searchResult : '#search-result'
|
||||
},
|
||||
|
||||
ui : {
|
||||
seriesSearch : '.x-series-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 AddSeriesCollection();
|
||||
console.log('this.collection:', this.collection);
|
||||
|
||||
if (this.isExisting) {
|
||||
this.collection.unmappedFolderModel = this.model;
|
||||
}
|
||||
|
||||
if (this.isExisting) {
|
||||
this.className = 'existing-series';
|
||||
} else {
|
||||
this.className = 'new-series';
|
||||
}
|
||||
|
||||
this.listenTo(vent, vent.Events.SeriesAdded, this._onSeriesAdded);
|
||||
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.seriesSearch.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.seriesSearch.val()
|
||||
});
|
||||
});
|
||||
|
||||
this._clearResults();
|
||||
|
||||
if (this.isExisting) {
|
||||
this.ui.searchBar.hide();
|
||||
}
|
||||
},
|
||||
|
||||
onShow : function() {
|
||||
this.ui.seriesSearch.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;
|
||||
},
|
||||
|
||||
_onSeriesAdded : function(options) {
|
||||
if (this.isExisting && options.series.get('path') === this.model.get('folder').path) {
|
||||
this.close();
|
||||
}
|
||||
|
||||
else if (!this.isExisting) {
|
||||
this.collection.term = '';
|
||||
this.collection.reset();
|
||||
this._clearResults();
|
||||
this.ui.seriesSearch.val('');
|
||||
this.ui.seriesSearch.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 = '';
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,24 +0,0 @@
|
|||
{{#if folder.path}}
|
||||
<div class="unmapped-folder-path">
|
||||
<div class="col-md-12">
|
||||
{{folder.path}}
|
||||
</div>
|
||||
</div>{{/if}}
|
||||
<div class="x-search-bar">
|
||||
<div class="input-group input-group-lg add-series-search">
|
||||
<span class="input-group-addon"><i class="icon-lidarr-search"/></span>
|
||||
|
||||
{{#if folder}}
|
||||
<input type="text" class="form-control x-series-search" value="{{folder.name}}">
|
||||
{{else}}
|
||||
<input type="text" class="form-control x-series-search" placeholder="Start typing the name of an artist or album...">
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div id="search-result" class="result-list col-md-12"/>
|
||||
</div>
|
||||
<div class="btn btn-block text-center new-series-loadmore x-load-more" style="display: none;">
|
||||
<i class="icon-lidarr-load-more"/>
|
||||
more
|
||||
</div>
|
|
@ -1,5 +0,0 @@
|
|||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
template : 'AddSeries/EmptyViewTemplate'
|
||||
});
|
|
@ -1,3 +0,0 @@
|
|||
<div class="text-center hint col-md-12">
|
||||
<span>You can also search by iTunes using the itunes: prefixes.</span>
|
||||
</div>
|
|
@ -1,13 +0,0 @@
|
|||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
template : 'AddSeries/ErrorViewTemplate',
|
||||
|
||||
initialize : function(options) {
|
||||
this.options = options;
|
||||
},
|
||||
|
||||
templateHelpers : function() {
|
||||
return this.options;
|
||||
}
|
||||
});
|
|
@ -1,7 +0,0 @@
|
|||
<div class="text-center col-md-12">
|
||||
<h3>
|
||||
There was an error searching for '{{term}}'.
|
||||
</h3>
|
||||
|
||||
If the artist name contains non-alphanumeric characters try removing them, otherwise try your search again later.
|
||||
</div>
|
|
@ -1,51 +0,0 @@
|
|||
var Marionette = require('marionette');
|
||||
var AddSeriesView = require('../AddSeriesView');
|
||||
var UnmappedFolderCollection = require('./UnmappedFolderCollection');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
itemView : AddSeriesView,
|
||||
itemViewContainer : '.x-loading-folders',
|
||||
template : 'AddSeries/Existing/AddExistingSeriesCollectionViewTemplate',
|
||||
|
||||
ui : {
|
||||
loadingFolders : '.x-loading-folders'
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
this.collection = new UnmappedFolderCollection();
|
||||
this.collection.importItems(this.model);
|
||||
},
|
||||
|
||||
showCollection : function() {
|
||||
this._showAndSearch(0);
|
||||
},
|
||||
|
||||
appendHtml : function(collectionView, itemView, index) {
|
||||
collectionView.ui.loadingFolders.before(itemView.el);
|
||||
},
|
||||
|
||||
_showAndSearch : function(index) {
|
||||
var self = this;
|
||||
var model = this.collection.at(index);
|
||||
|
||||
if (model) {
|
||||
var currentIndex = index;
|
||||
var folderName = model.get('folder').name;
|
||||
this.addItemView(model, this.getItemView(), index);
|
||||
this.children.findByModel(model).search({ term : folderName }).always(function() {
|
||||
if (!self.isClosed) {
|
||||
self._showAndSearch(currentIndex + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
else {
|
||||
this.ui.loadingFolders.hide();
|
||||
}
|
||||
},
|
||||
|
||||
itemViewOptions : {
|
||||
isExisting : true
|
||||
}
|
||||
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
<div class="x-existing-folders">
|
||||
<div class="loading-folders x-loading-folders">
|
||||
Loading search results from iTunes for your artists, this may take a few minutes.
|
||||
</div>
|
||||
</div>
|
|
@ -1,20 +0,0 @@
|
|||
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);
|
||||
}
|
||||
});
|
|
@ -1,3 +0,0 @@
|
|||
var Backbone = require('backbone');
|
||||
|
||||
module.exports = Backbone.Model.extend({});
|
|
@ -1,18 +0,0 @@
|
|||
<dl class="monitor-tooltip-contents">
|
||||
<dt>All</dt>
|
||||
<dd>Monitor all episodes except specials</dd>
|
||||
<dt>Future</dt>
|
||||
<dd>Monitor episodes that have not aired yet</dd>
|
||||
<dt>Missing</dt>
|
||||
<dd>Monitor episodes that do not have files or have not aired yet</dd>
|
||||
<dt>Existing</dt>
|
||||
<dd>Monitor episodes that have files or have not aired yet</dd>
|
||||
<dt>First Season</dt>
|
||||
<dd>Monitor all episodes of the first season. All other seasons will be ignored</dd>
|
||||
<dt>Latest Season</dt>
|
||||
<dd>Monitor all episodes of the latest season and future seasons</dd>
|
||||
<dt>None</dt>
|
||||
<dd>No episodes will be monitored.</dd>
|
||||
<!--<dt>Latest Season</dt>-->
|
||||
<!--<dd>Monitor all episodes the latest season only, previous seasons will be ignored</dd>-->
|
||||
</dl>
|
|
@ -1,13 +0,0 @@
|
|||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
template : 'AddSeries/NotFoundViewTemplate',
|
||||
|
||||
initialize : function(options) {
|
||||
this.options = options;
|
||||
},
|
||||
|
||||
templateHelpers : function() {
|
||||
return this.options;
|
||||
}
|
||||
});
|
|
@ -1,7 +0,0 @@
|
|||
<div class="text-center col-md-12">
|
||||
<h3>
|
||||
Sorry. We couldn't find any series matching '{{term}}'
|
||||
</h3>
|
||||
<a href="https://github.com/NzbDrone/NzbDrone/wiki/FAQ#wiki-why-cant-i-add-a-new-show-to-nzbdrone-its-on-thetvdb">Why can't I find my show?</a>
|
||||
|
||||
</div>
|
|
@ -1,10 +0,0 @@
|
|||
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();
|
|
@ -1,8 +0,0 @@
|
|||
var Marionette = require('marionette');
|
||||
var RootFolderItemView = require('./RootFolderItemView');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
template : 'AddSeries/RootFolders/RootFolderCollectionViewTemplate',
|
||||
itemViewContainer : '.x-root-folders',
|
||||
itemView : RootFolderItemView
|
||||
});
|
|
@ -1,13 +0,0 @@
|
|||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-md-10 ">
|
||||
Path
|
||||
</th>
|
||||
<th class="col-md-3">
|
||||
Free Space
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="x-root-folders"></tbody>
|
||||
</table>
|
|
@ -1,28 +0,0 @@
|
|||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.ItemView.extend({
|
||||
template : 'AddSeries/RootFolders/RootFolderItemViewTemplate',
|
||||
className : 'recent-folder',
|
||||
tagName : 'tr',
|
||||
|
||||
initialize : function() {
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-delete' : 'removeFolder',
|
||||
'click .x-folder' : 'folderSelected'
|
||||
},
|
||||
|
||||
removeFolder : function() {
|
||||
var self = this;
|
||||
|
||||
this.model.destroy().success(function() {
|
||||
self.close();
|
||||
});
|
||||
},
|
||||
|
||||
folderSelected : function() {
|
||||
this.trigger('folderSelected', this.model);
|
||||
}
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
<td class="col-md-10 x-folder folder-path">
|
||||
{{path}}
|
||||
</td>
|
||||
<td class="col-md-3 x-folder folder-free-space">
|
||||
<span>{{Bytes freeSpace}}</span>
|
||||
</td>
|
||||
<td class="col-md-1">
|
||||
<i class="icon-lidarr-delete x-delete"></i>
|
||||
</td>
|
|
@ -1,80 +0,0 @@
|
|||
var Marionette = require('marionette');
|
||||
var RootFolderCollectionView = require('./RootFolderCollectionView');
|
||||
var RootFolderCollection = require('./RootFolderCollection');
|
||||
var RootFolderModel = require('./RootFolderModel');
|
||||
var LoadingView = require('../../Shared/LoadingView');
|
||||
var AsValidatedView = require('../../Mixins/AsValidatedView');
|
||||
require('../../Mixins/FileBrowser');
|
||||
|
||||
var Layout = Marionette.Layout.extend({
|
||||
template : 'AddSeries/RootFolders/RootFolderLayoutTemplate',
|
||||
|
||||
ui : {
|
||||
pathInput : '.x-path'
|
||||
},
|
||||
|
||||
regions : {
|
||||
currentDirs : '#current-dirs'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-add' : '_addFolder',
|
||||
'keydown .x-path input' : '_keydown'
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
this.collection = RootFolderCollection;
|
||||
this.rootfolderListView = 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;
|
|
@ -1,36 +0,0 @@
|
|||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h3>Select Folder</h3>
|
||||
</div>
|
||||
<div class="modal-body root-folders-modal">
|
||||
<div class="validation-errors"></div>
|
||||
<div class="alert alert-info">Enter the path that contains some or all of your TV series, you will be able to choose which series you want to import<button type="button" class="close" data-dismiss="alert">×</button></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"> <i class="icon-lidarr-folder-open"></i></span>
|
||||
<input class="form-control x-path" type="text" validation-name="path" placeholder="Enter path to folder that contains your shows">
|
||||
<span class="input-group-btn"><button class="btn btn-success x-add"><i class="icon-lidarr-ok"/></button></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row root-folders">
|
||||
<div class="col-md-12">
|
||||
{{#if items}}
|
||||
<h4>Recent Folders</h4>
|
||||
{{/if}}
|
||||
<div id="current-dirs" class="root-folders-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
|
@ -1,8 +0,0 @@
|
|||
var Backbone = require('backbone');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
urlRoot : window.NzbDrone.ApiRoot + '/rootfolder',
|
||||
defaults : {
|
||||
freeSpace : 0
|
||||
}
|
||||
});
|
|
@ -1,11 +0,0 @@
|
|||
<select class="col-md-4 form-control x-root-folder" validation-name="RootFolderPath">
|
||||
{{#if this}}
|
||||
{{#each this}}
|
||||
<option value="{{id}}">{{path}}</option>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<option value="">Select Path</option>
|
||||
{{/if}}
|
||||
<option value="addNew">Add a different path</option>
|
||||
</select>
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,297 +0,0 @@
|
|||
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 : 'AddSeries/SearchResultViewTemplate',
|
||||
|
||||
ui : {
|
||||
profile : '.x-profile',
|
||||
rootFolder : '.x-root-folder',
|
||||
seasonFolder : '.x-season-folder',
|
||||
seriesType : '.x-series-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-season-folder' : '_seasonFolderChanged',
|
||||
'change .x-series-type' : '_seriesTypeChanged',
|
||||
'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 defaultSeriesType = 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.seasonFolder.prop('checked', useSeasonFolder);
|
||||
this.ui.seriesType.val(defaultSeriesType);
|
||||
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('AddSeries/MonitoringTooltipTemplate');
|
||||
var content = this.templateFunction();
|
||||
|
||||
this.ui.monitorTooltip.popover({
|
||||
content : content,
|
||||
html : true,
|
||||
trigger : 'hover',
|
||||
title : 'Episode Monitoring Options',
|
||||
placement : 'right',
|
||||
container : this.$el
|
||||
});
|
||||
},
|
||||
|
||||
_configureTemplateHelpers : function() {
|
||||
var existingSeries = ArtistCollection.where({ iTunesId : this.model.get('itunesId') });
|
||||
|
||||
if (existingSeries.length > 0) {
|
||||
this.templateHelpers.existing = existingSeries[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.UseSeasonFolder) {
|
||||
this.ui.seasonFolder.prop('checked', options.value);
|
||||
}
|
||||
|
||||
else if (options.key === Config.Keys.DefaultSeriesType) {
|
||||
this.ui.seriesType.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());
|
||||
},
|
||||
|
||||
_seasonFolderChanged : function() {
|
||||
Config.setValue(Config.Keys.UseSeasonFolder, this.ui.seasonFolder.prop('checked'));
|
||||
},
|
||||
|
||||
_rootFolderChanged : function() {
|
||||
var rootFolderValue = this.ui.rootFolder.val();
|
||||
if (rootFolderValue === 'addNew') {
|
||||
var rootFolderLayout = new RootFolderLayout();
|
||||
this.listenToOnce(rootFolderLayout, 'folderSelected', this._setRootFolder);
|
||||
AppLayout.modalRegion.show(rootFolderLayout);
|
||||
} else {
|
||||
Config.setValue(Config.Keys.DefaultRootFolderId, rootFolderValue);
|
||||
}
|
||||
},
|
||||
|
||||
_seriesTypeChanged : function() {
|
||||
Config.setValue(Config.Keys.DefaultSeriesType, this.ui.seriesType.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._addSeries(false);
|
||||
},
|
||||
|
||||
_addAndSearch : function() {
|
||||
this._addSeries(true);
|
||||
},
|
||||
|
||||
_addSeries : function(searchForMissing) {
|
||||
// TODO: Refactor to handle multiple add buttons/albums
|
||||
var addButton = this.ui.addButton;
|
||||
var addSearchButton = this.ui.addSearchButton;
|
||||
console.log('_addSeries, searchForMissing=', searchForMissing);
|
||||
|
||||
addButton.addClass('disabled');
|
||||
addSearchButton.addClass('disabled');
|
||||
|
||||
var profile = this.ui.profile.val();
|
||||
var rootFolderPath = this.ui.rootFolder.children(':selected').text();
|
||||
var seriesType = this.ui.seriesType.val(); // Perhaps make this a differnitator between artist or Album?
|
||||
var seasonFolder = this.ui.seasonFolder.prop('checked');
|
||||
|
||||
var options = this._getAddSeriesOptions();
|
||||
options.searchForMissing = searchForMissing;
|
||||
|
||||
this.model.set({
|
||||
profileId : profile,
|
||||
rootFolderPath : rootFolderPath,
|
||||
seasonFolder : seasonFolder,
|
||||
seriesType : seriesType,
|
||||
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] _addSeries promise resolve:', self.model);
|
||||
ArtistCollection.add(self.model);
|
||||
|
||||
self.close();
|
||||
|
||||
Messenger.show({
|
||||
message : 'Added: ' + self.model.get('artistName'),
|
||||
actions : {
|
||||
goToSeries : {
|
||||
label : 'Go to Artist',
|
||||
action : function() {
|
||||
Backbone.history.navigate('/artist/' + self.model.get('artistSlug'), { trigger : true });
|
||||
}
|
||||
}
|
||||
},
|
||||
hideAfter : 8,
|
||||
hideOnNavigate : true
|
||||
});
|
||||
|
||||
vent.trigger(vent.Events.SeriesAdded, { series : self.model });
|
||||
});
|
||||
},
|
||||
|
||||
_rootFoldersUpdated : function() {
|
||||
this._configureTemplateHelpers();
|
||||
this.render();
|
||||
},
|
||||
|
||||
_getAddSeriesOptions : 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 = {
|
||||
ignoreEpisodesWithFiles : false,
|
||||
ignoreEpisodesWithoutFiles : false
|
||||
};
|
||||
|
||||
if (monitor === 'all') {
|
||||
return options;
|
||||
}
|
||||
|
||||
else if (monitor === 'future') {
|
||||
options.ignoreEpisodesWithFiles = true;
|
||||
options.ignoreEpisodesWithoutFiles = true;
|
||||
}
|
||||
|
||||
/*else if (monitor === 'latest') {
|
||||
this.model.setSeasonPass(lastSeason.seasonNumber);
|
||||
}
|
||||
|
||||
else if (monitor === 'first') {
|
||||
this.model.setSeasonPass(lastSeason.seasonNumber + 1);
|
||||
this.model.setSeasonMonitored(firstSeason.seasonNumber);
|
||||
}*/
|
||||
|
||||
else if (monitor === 'missing') {
|
||||
options.ignoreEpisodesWithFiles = true;
|
||||
}
|
||||
|
||||
else if (monitor === 'existing') {
|
||||
options.ignoreEpisodesWithoutFiles = true;
|
||||
}
|
||||
|
||||
/*else if (monitor === 'none') {
|
||||
this.model.setSeasonPass(lastSeason.seasonNumber + 1);
|
||||
}*/
|
||||
|
||||
return options;
|
||||
}
|
||||
});
|
||||
|
||||
AsValidatedView.apply(view);
|
||||
|
||||
module.exports = view;
|
|
@ -1,146 +0,0 @@
|
|||
<div class="search-item {{#unless isExisting}}search-item-new{{/unless}}">
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h2 class="series-title">
|
||||
<!--{{titleWithYear}}-->
|
||||
{{artistName}}
|
||||
|
||||
<!--<span class="labels">
|
||||
<span class="label label-default">{{network}}</span>
|
||||
{{#unless_eq status compare="continuing"}}
|
||||
<span class="label label-danger">Ended</span>
|
||||
{{/unless_eq}}
|
||||
</span>-->
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="row new-series-overview x-overview">
|
||||
<div class="col-md-12 overview-internal">
|
||||
{{overview}}
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="row">
|
||||
{{#unless existing}}
|
||||
{{#unless path}}
|
||||
<div class="form-group col-md-4">
|
||||
<label>Path</label>
|
||||
{{> RootFolderSelectionPartial rootFolders}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label>Monitor <i class="icon-lidarr-form-info monitor-tooltip x-monitor-tooltip"></i></label>
|
||||
<select class="form-control col-md-2 x-monitor">
|
||||
<option value="all">All</option>
|
||||
<option value="future">Future</option>
|
||||
<option value="missing">Missing</option>
|
||||
<option value="existing">Existing</option>
|
||||
<option value="none">None</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label>Profile</label>
|
||||
{{> ProfileSelectionPartial profiles}}
|
||||
</div>
|
||||
|
||||
<!--<div class="form-group col-md-2">
|
||||
|
||||
</div>-->
|
||||
|
||||
<div class="form-group col-md-2">
|
||||
<label>Album Folders</label>
|
||||
|
||||
<div class="input-group">
|
||||
<label class="checkbox toggle well">
|
||||
<input type="checkbox" class="x-season-folder"/>
|
||||
<p>
|
||||
<span>Yes</span>
|
||||
<span>No</span>
|
||||
</p>
|
||||
<div class="btn btn-primary slide-button"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
<div class="row">
|
||||
{{#unless existing}}
|
||||
{{#if artistName}}
|
||||
<div class="form-group col-md-2 col-md-offset-10">
|
||||
<!--Uncomment if we need to add even more controls to add series-->
|
||||
<!--<label style="visibility: hidden">Add</label>-->
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-success add x-add" title="Add" data-artist="{{artistName}}">
|
||||
<i class="icon-lidarr-add"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-success add x-add-search" title="Add and Search for missing tracks" data-artist="{{artistName}}">
|
||||
<i class="icon-lidarr-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="col-md-2 col-md-offset-10">
|
||||
<button class="btn add-series disabled">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<div class="col-md-2 col-md-offset-10">
|
||||
<a class="btn btn-default" href="{{route}}">
|
||||
Already Exists
|
||||
</a>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{{#each albums}}
|
||||
<div class="col-md-12" style="border:1px dashed black;">
|
||||
<div class="col-md-2">
|
||||
<a href="{{artworkUrl}}" target="_blank">
|
||||
<!-- {{poster}} -->
|
||||
<img class="album-poster" src="{{artworkUrl}}">
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<h2>{{albumName}} ({{year}})</h2>
|
||||
{{#unless existing}}
|
||||
{{#if albumName}}
|
||||
<div class="form-group col-md-offset-10">
|
||||
<!--Uncomment if we need to add even more controls to add series-->
|
||||
<!--<label style="visibility: hidden">Add</label>-->
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-success add x-add-album" title="Add" data-album="{{albumName}}">
|
||||
<i class="icon-lidarr-add"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-success add x-add-album-search" title="Add and Search for missing tracks" data-album="{{albumName}}">
|
||||
<i class="icon-lidarr-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="col-md-2 col-md-offset-10">
|
||||
<button class="btn add-series disabled">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<div class="col-md-2 col-md-offset-10">
|
||||
<a class="btn btn-default" href="{{route}}">
|
||||
Already Exists
|
||||
</a>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
|
@ -1,5 +0,0 @@
|
|||
<select class="form-control col-md-2 x-series-type" name="seriesType">
|
||||
<option value="standard">Standard</option>
|
||||
<option value="daily">Daily</option>
|
||||
<option value="anime">Anime</option>
|
||||
</select>
|
|
@ -1,13 +0,0 @@
|
|||
<select class="form-control col-md-2 starting-season x-starting-season">
|
||||
|
||||
|
||||
{{#each this}}
|
||||
{{#if_eq seasonNumber compare="0"}}
|
||||
<option value="{{seasonNumber}}">Specials</option>
|
||||
{{else}}
|
||||
<option value="{{seasonNumber}}">Season {{seasonNumber}}</option>
|
||||
{{/if_eq}}
|
||||
{{/each}}
|
||||
|
||||
<option value="5000000">None</option>
|
||||
</select>
|
|
@ -1,181 +0,0 @@
|
|||
@import "../Shared/Styles/card.less";
|
||||
@import "../Shared/Styles/clickable.less";
|
||||
|
||||
#add-series-screen {
|
||||
.existing-series {
|
||||
|
||||
.card();
|
||||
margin : 30px 0px;
|
||||
|
||||
.unmapped-folder-path {
|
||||
padding: 20px;
|
||||
margin-left : 0px;
|
||||
font-weight : 100;
|
||||
font-size : 25px;
|
||||
text-align : center;
|
||||
}
|
||||
|
||||
.new-series-loadmore {
|
||||
font-size : 30px;
|
||||
font-weight : 300;
|
||||
padding-top : 10px;
|
||||
padding-bottom : 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.new-series {
|
||||
.search-item {
|
||||
.card();
|
||||
margin : 40px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.add-series-search {
|
||||
margin-top : 20px;
|
||||
margin-bottom : 20px;
|
||||
}
|
||||
|
||||
.search-item {
|
||||
|
||||
padding-bottom : 20px;
|
||||
|
||||
.series-title {
|
||||
margin-top : 5px;
|
||||
|
||||
.labels {
|
||||
margin-left : 10px;
|
||||
|
||||
.label {
|
||||
font-size : 12px;
|
||||
vertical-align : middle;
|
||||
}
|
||||
}
|
||||
|
||||
.year {
|
||||
font-style : italic;
|
||||
color : #aaaaaa;
|
||||
}
|
||||
}
|
||||
|
||||
.new-series-overview {
|
||||
overflow : hidden;
|
||||
height : 103px;
|
||||
|
||||
.overview-internal {
|
||||
overflow : hidden;
|
||||
height : 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.series-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();
|
||||
}
|
||||
}
|
|
@ -2,9 +2,27 @@ var Backbone = require('backbone');
|
|||
var AlbumModel = require('./AlbumModel');
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/album',
|
||||
model : AlbumModel,
|
||||
|
||||
comparator : function(season) {
|
||||
return -season.get('seasonNumber');
|
||||
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 = { artistId : this.artistId };
|
||||
|
||||
return this.originalFetch.call(this, options);
|
||||
}
|
||||
});
|
|
@ -1,11 +1,8 @@
|
|||
var Backbone = require('backbone');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults : {
|
||||
seasonNumber : 0
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
this.set('id', this.get('seasonNumber'));
|
||||
}
|
||||
defaults : {
|
||||
artistId : 0
|
||||
},
|
||||
});
|
|
@ -67,7 +67,7 @@ var Collection = PageableCollection.extend({
|
|||
'missing' : [
|
||||
null,
|
||||
null,
|
||||
function(model) { return model.get('episodeCount') !== model.get('episodeFileCount'); }
|
||||
function(model) { return model.get('trackCount') !== model.get('trackFileCount'); }
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -96,12 +96,12 @@ var Collection = PageableCollection.extend({
|
|||
}
|
||||
},
|
||||
|
||||
percentOfEpisodes : {
|
||||
percentOfTracks : {
|
||||
sortValue : function(model, attr) {
|
||||
var percentOfEpisodes = model.get(attr);
|
||||
var episodeCount = model.get('episodeCount');
|
||||
var percentOfTracks = model.get(attr);
|
||||
var trackCount = model.get('trackCount');
|
||||
|
||||
return percentOfEpisodes + episodeCount / 1000000;
|
||||
return percentOfTracks + trackCount / 1000000;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -5,14 +5,15 @@ module.exports = Backbone.Model.extend({
|
|||
urlRoot : window.NzbDrone.ApiRoot + '/artist',
|
||||
|
||||
defaults : {
|
||||
episodeFileCount : 0,
|
||||
episodeCount : 0,
|
||||
trackFileCount : 0,
|
||||
trackCount : 0,
|
||||
isExisting : false,
|
||||
status : 0
|
||||
},
|
||||
|
||||
setAlbumsMonitored : function(albumName) {
|
||||
_.each(this.get('albums'), function(album) {
|
||||
console.log(album);
|
||||
if (album.albumName === albumName) {
|
||||
album.monitored = !album.monitored;
|
||||
}
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
var _ = require('underscore');
|
||||
var Marionette = require('marionette');
|
||||
var SeasonLayout = require('./AlbumLayout');
|
||||
var AlbumLayout = require('./AlbumLayout');
|
||||
var AsSortedCollectionView = require('../../Mixins/AsSortedCollectionView');
|
||||
|
||||
var view = Marionette.CollectionView.extend({
|
||||
|
||||
itemView : SeasonLayout,
|
||||
itemView : AlbumLayout,
|
||||
|
||||
initialize : function(options) {
|
||||
if (!options.trackCollection) {
|
||||
throw 'trackCollection is needed';
|
||||
}
|
||||
|
||||
console.log(options);
|
||||
this.albumCollection = options.collection;
|
||||
this.trackCollection = options.trackCollection;
|
||||
this.artist = options.artist;
|
||||
},
|
||||
|
||||
itemViewOptions : function() {
|
||||
return {
|
||||
albumCollection : this.albumCollection,
|
||||
trackCollection : this.trackCollection,
|
||||
artist : this.artist
|
||||
};
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.ItemView.extend({
|
||||
template : 'Artist/Details/AlbumInfoViewTemplate',
|
||||
|
||||
initialize : function(options) {
|
||||
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
},
|
||||
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
{{profile profileId}}
|
||||
|
||||
{{#if label}}
|
||||
<span class="label label-info">{{label}}</span>
|
||||
{{/if}}
|
||||
|
||||
<span class="label label-info">{{path}}</span>
|
||||
|
||||
{{#if ratings}}
|
||||
<span class="label label-info" title="{{ratings.votes}} vote{{#if_gt ratings.votes compare="1"}}s{{/if_gt}}">{{ratings.value}}</span>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<span class="album-info-links">
|
||||
<a href="{{MBAlbumUrl}}" class="label label-info">MusicBrainz</a>
|
||||
|
||||
{{#if tadbId}}
|
||||
<a href="{{TADBAlbumUrl}}" class="label label-info">The AudioDB</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if discogsId}}
|
||||
<a href="{{discogsAlbumUrl}}" class="label label-info">Discogs</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if amId}}
|
||||
<a href="{{allMusicAlbumUrl}}" class="label label-info">AllMusic</a>
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if tags}}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
{{tagDisplay tags}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -1,39 +1,44 @@
|
|||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
var Backgrid = require('backgrid');
|
||||
var ToggleCell = require('../../Cells/EpisodeMonitoredCell');
|
||||
var EpisodeTitleCell = require('../../Cells/EpisodeTitleCell');
|
||||
var ToggleCell = require('../../Cells/TrackMonitoredCell');
|
||||
var TrackTitleCell = require('../../Cells/TrackTitleCell');
|
||||
var TrackExplicitCell = require('../../Cells/TrackExplicitCell');
|
||||
var RelativeDateCell = require('../../Cells/RelativeDateCell');
|
||||
var EpisodeStatusCell = require('../../Cells/EpisodeStatusCell');
|
||||
var EpisodeActionsCell = require('../../Cells/EpisodeActionsCell');
|
||||
var TrackStatusCell = require('../../Cells/TrackStatusCell');
|
||||
var TrackActionsCell = require('../../Cells/TrackActionsCell');
|
||||
var TrackNumberCell = require('./TrackNumberCell');
|
||||
var TrackWarningCell = require('./TrackWarningCell');
|
||||
var TrackRatingCell = require('./TrackRatingCell');
|
||||
var AlbumInfoView = require('./AlbumInfoView');
|
||||
var CommandController = require('../../Commands/CommandController');
|
||||
var EpisodeFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEditorLayout');
|
||||
//var TrackFileEditorLayout = require('../../TrackFile/Editor/TrackFileEditorLayout');
|
||||
var moment = require('moment');
|
||||
var _ = require('underscore');
|
||||
var Messenger = require('../../Shared/Messenger');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'Artist/Details/ArtistLayoutTemplate',
|
||||
template : 'Artist/Details/AlbumLayoutTemplate',
|
||||
|
||||
ui : {
|
||||
seasonSearch : '.x-album-search',
|
||||
seasonMonitored : '.x-album-monitored',
|
||||
seasonRename : '.x-album-rename'
|
||||
albumSearch : '.x-album-search',
|
||||
albumMonitored : '.x-album-monitored',
|
||||
albumRename : '.x-album-rename',
|
||||
cover : '.x-album-cover'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-album-episode-file-editor' : '_openEpisodeFileEditor',
|
||||
'click .x-track-file-editor' : '_openTrackFileEditor',
|
||||
'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'
|
||||
'click .x-show-hide-tracks' : '_showHideTracks',
|
||||
'dblclick .artist-album h2' : '_showHideTracks'
|
||||
},
|
||||
|
||||
regions : {
|
||||
episodeGrid : '.x-episode-grid'
|
||||
trackGrid : '.x-track-grid',
|
||||
albumInfo : '#album-info'
|
||||
},
|
||||
|
||||
columns : [
|
||||
|
@ -61,110 +66,135 @@ module.exports = Marionette.Layout.extend({
|
|||
{
|
||||
name : 'this',
|
||||
label : 'Title',
|
||||
hideSeriesLink : true,
|
||||
cell : EpisodeTitleCell,
|
||||
hideArtistLink : true,
|
||||
cell : TrackTitleCell,
|
||||
sortable : false
|
||||
},
|
||||
{
|
||||
name : 'airDateUtc',
|
||||
label : 'Air Date',
|
||||
cell : RelativeDateCell
|
||||
name : 'this',
|
||||
label : 'Rating',
|
||||
cell : TrackRatingCell
|
||||
},
|
||||
{
|
||||
name : 'status',
|
||||
label : 'Status',
|
||||
cell : EpisodeStatusCell,
|
||||
sortable : false
|
||||
name : 'this',
|
||||
label : 'Content',
|
||||
cell : TrackExplicitCell
|
||||
},
|
||||
{
|
||||
name : 'this',
|
||||
label : '',
|
||||
cell : EpisodeActionsCell,
|
||||
sortable : false
|
||||
}
|
||||
//{
|
||||
// name : 'airDateUtc',
|
||||
// label : 'Air Date',
|
||||
// cell : RelativeDateCell
|
||||
//},
|
||||
{
|
||||
name : 'status',
|
||||
label : 'Status',
|
||||
cell : TrackStatusCell,
|
||||
sortable : false
|
||||
},
|
||||
//{
|
||||
// name : 'this',
|
||||
// label : '',
|
||||
// cell : TrackActionsCell,
|
||||
// sortable : false
|
||||
//}
|
||||
],
|
||||
|
||||
templateHelpers : function() {
|
||||
var episodeCount = this.episodeCollection.filter(function(episode) {
|
||||
return episode.get('hasFile') || episode.get('monitored') && moment(episode.get('airDateUtc')).isBefore(moment());
|
||||
var trackCount = this.trackCollection.filter(function(track) {
|
||||
return track.get('hasFile') || track.get('monitored');
|
||||
}).length;
|
||||
|
||||
var episodeFileCount = this.episodeCollection.where({ hasFile : true }).length;
|
||||
var percentOfEpisodes = 100;
|
||||
var trackFileCount = this.trackCollection.where({ hasFile : true }).length;
|
||||
var percentOfTracks = 100;
|
||||
|
||||
if (episodeCount > 0) {
|
||||
percentOfEpisodes = episodeFileCount / episodeCount * 100;
|
||||
if (trackCount > 0) {
|
||||
percentOfTracks = trackFileCount / trackCount * 100;
|
||||
}
|
||||
|
||||
return {
|
||||
showingEpisodes : this.showingEpisodes,
|
||||
episodeCount : episodeCount,
|
||||
episodeFileCount : episodeFileCount,
|
||||
percentOfEpisodes : percentOfEpisodes
|
||||
showingTracks : this.showingTracks,
|
||||
trackCount : trackCount,
|
||||
trackFileCount : trackFileCount,
|
||||
percentOfTracks : percentOfTracks
|
||||
};
|
||||
},
|
||||
|
||||
initialize : function(options) {
|
||||
if (!options.episodeCollection) {
|
||||
throw 'episodeCollection is required';
|
||||
if (!options.trackCollection) {
|
||||
throw 'trackCollection is required';
|
||||
}
|
||||
|
||||
this.artist = options.artist;
|
||||
this.fullTrackCollection = options.trackCollection;
|
||||
|
||||
this.trackCollection = this.fullTrackCollection.byAlbum(this.model.get('id'));
|
||||
this._updateTrackCollection();
|
||||
|
||||
this.series = options.series;
|
||||
this.fullEpisodeCollection = options.episodeCollection;
|
||||
this.episodeCollection = this.fullEpisodeCollection.bySeason(this.model.get('seasonNumber'));
|
||||
this._updateEpisodeCollection();
|
||||
console.log(options);
|
||||
|
||||
this.showingEpisodes = this._shouldShowEpisodes();
|
||||
this.showingTracks = this._shouldShowTracks();
|
||||
|
||||
this.listenTo(this.model, 'sync', this._afterSeasonMonitored);
|
||||
this.listenTo(this.episodeCollection, 'sync', this.render);
|
||||
|
||||
this.listenTo(this.fullEpisodeCollection, 'sync', this._refreshEpisodes);
|
||||
this.listenTo(this.trackCollection, 'sync', this.render);
|
||||
this.listenTo(this.fullTrackCollection, 'sync', this._refreshTracks);
|
||||
this.listenTo(this.model, 'change:images', this._updateImages);
|
||||
},
|
||||
|
||||
onRender : function() {
|
||||
if (this.showingEpisodes) {
|
||||
this._showEpisodes();
|
||||
if (this.showingTracks) {
|
||||
this._showTracks();
|
||||
}
|
||||
|
||||
this._setSeasonMonitoredState();
|
||||
this._showAlbumInfo();
|
||||
|
||||
this._setAlbumMonitoredState();
|
||||
|
||||
CommandController.bindToCommand({
|
||||
element : this.ui.seasonSearch,
|
||||
element : this.ui.albumSearch,
|
||||
command : {
|
||||
name : 'seasonSearch',
|
||||
seriesId : this.series.id,
|
||||
seasonNumber : this.model.get('seasonNumber')
|
||||
name : 'albumSearch',
|
||||
artistId : this.artist.id,
|
||||
albumId : this.model.get('albumId')
|
||||
}
|
||||
});
|
||||
|
||||
CommandController.bindToCommand({
|
||||
element : this.ui.seasonRename,
|
||||
element : this.ui.albumRename,
|
||||
command : {
|
||||
name : 'renameFiles',
|
||||
seriesId : this.series.id,
|
||||
seasonNumber : this.model.get('seasonNumber')
|
||||
artistId : this.artist.id,
|
||||
albumId : this.model.get('albumId')
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_seasonSearch : function() {
|
||||
CommandController.Execute('seasonSearch', {
|
||||
name : 'seasonSearch',
|
||||
seriesId : this.series.id,
|
||||
seasonNumber : this.model.get('seasonNumber')
|
||||
_getImage : function(type) {
|
||||
var image = _.where(this.model.get('images'), { coverType : type });
|
||||
|
||||
if (image && image[0]) {
|
||||
return image[0].url;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
_albumSearch : function() {
|
||||
CommandController.Execute('albumSearch', {
|
||||
name : 'albumSearch',
|
||||
artistId : this.artist.id,
|
||||
albumId : this.model.get('albumId')
|
||||
});
|
||||
},
|
||||
|
||||
_seasonRename : function() {
|
||||
_albumRename : function() {
|
||||
vent.trigger(vent.Commands.ShowRenamePreview, {
|
||||
series : this.series,
|
||||
seasonNumber : this.model.get('seasonNumber')
|
||||
artist : this.artist,
|
||||
albumId : this.model.get('albumId')
|
||||
});
|
||||
},
|
||||
|
||||
_seasonMonitored : function() {
|
||||
if (!this.series.get('monitored')) {
|
||||
_albumMonitored : function() {
|
||||
if (!this.artist.get('monitored')) {
|
||||
|
||||
Messenger.show({
|
||||
message : 'Unable to change monitored state when series is not monitored',
|
||||
|
@ -176,80 +206,86 @@ module.exports = Marionette.Layout.extend({
|
|||
|
||||
var name = 'monitored';
|
||||
this.model.set(name, !this.model.get(name));
|
||||
this.series.setSeasonMonitored(this.model.get('seasonNumber'));
|
||||
this.artist.setSeasonMonitored(this.model.get('albumId'));
|
||||
|
||||
var savePromise = this.series.save().always(this._afterSeasonMonitored.bind(this));
|
||||
var savePromise = this.artist.save().always(this._afterSeasonMonitored.bind(this));
|
||||
|
||||
this.ui.seasonMonitored.spinForPromise(savePromise);
|
||||
this.ui.albumMonitored.spinForPromise(savePromise);
|
||||
},
|
||||
|
||||
_afterSeasonMonitored : function() {
|
||||
var self = this;
|
||||
|
||||
_.each(this.episodeCollection.models, function(episode) {
|
||||
episode.set({ monitored : self.model.get('monitored') });
|
||||
_.each(this.trackCollection.models, function(track) {
|
||||
track.set({ monitored : self.model.get('monitored') });
|
||||
});
|
||||
|
||||
this.render();
|
||||
},
|
||||
|
||||
_setSeasonMonitoredState : function() {
|
||||
this.ui.seasonMonitored.removeClass('icon-lidarr-spinner fa-spin');
|
||||
_setAlbumMonitoredState : function() {
|
||||
this.ui.albumMonitored.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');
|
||||
this.ui.albumMonitored.addClass('icon-lidarr-monitored');
|
||||
this.ui.albumMonitored.removeClass('icon-lidarr-unmonitored');
|
||||
} else {
|
||||
this.ui.seasonMonitored.addClass('icon-lidarr-unmonitored');
|
||||
this.ui.seasonMonitored.removeClass('icon-lidarr-monitored');
|
||||
this.ui.albumMonitored.addClass('icon-lidarr-unmonitored');
|
||||
this.ui.albumMonitored.removeClass('icon-lidarr-monitored');
|
||||
}
|
||||
},
|
||||
|
||||
_showEpisodes : function() {
|
||||
this.episodeGrid.show(new Backgrid.Grid({
|
||||
_showTracks : function() {
|
||||
this.trackGrid.show(new Backgrid.Grid({
|
||||
columns : this.columns,
|
||||
collection : this.episodeCollection,
|
||||
className : 'table table-hover season-grid'
|
||||
collection : this.trackCollection,
|
||||
className : 'table table-hover track-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;
|
||||
});
|
||||
_showAlbumInfo : function() {
|
||||
this.albumInfo.show(new AlbumInfoView({
|
||||
model : this.model
|
||||
}));
|
||||
},
|
||||
|
||||
_showHideEpisodes : function() {
|
||||
if (this.showingEpisodes) {
|
||||
this.showingEpisodes = false;
|
||||
this.episodeGrid.close();
|
||||
_shouldShowTracks : function() {
|
||||
var startDate = moment().add('month', -1);
|
||||
var endDate = moment().add('year', 1);
|
||||
return true;
|
||||
//return this.trackCollection.some(function(track) {
|
||||
// var airDate = track.get('releasedDate');
|
||||
|
||||
// if (airDate) {
|
||||
// var airDateMoment = moment(airDate);
|
||||
|
||||
// if (airDateMoment.isAfter(startDate) && airDateMoment.isBefore(endDate)) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return false;
|
||||
//});
|
||||
},
|
||||
|
||||
_showHideTracks : function() {
|
||||
if (this.showingTracks) {
|
||||
this.showingTracks = false;
|
||||
this.trackGrid.close();
|
||||
} else {
|
||||
this.showingEpisodes = true;
|
||||
this._showEpisodes();
|
||||
this.showingTracks = true;
|
||||
this._showTracks();
|
||||
}
|
||||
|
||||
this.templateHelpers.showingEpisodes = this.showingEpisodes;
|
||||
this.templateHelpers.showingTracks = this.showingTracks;
|
||||
this.render();
|
||||
},
|
||||
|
||||
_episodeMonitoredToggled : function(options) {
|
||||
_trackMonitoredToggled : function(options) {
|
||||
var model = options.model;
|
||||
var shiftKey = options.shiftKey;
|
||||
|
||||
if (!this.episodeCollection.get(model.get('id'))) {
|
||||
if (!this.trackCollection.get(model.get('id'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -257,45 +293,53 @@ module.exports = Marionette.Layout.extend({
|
|||
return;
|
||||
}
|
||||
|
||||
var lastToggled = this.episodeCollection.lastToggled;
|
||||
var lastToggled = this.trackCollection.lastToggled;
|
||||
|
||||
if (!lastToggled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentIndex = this.episodeCollection.indexOf(model);
|
||||
var lastIndex = this.episodeCollection.indexOf(lastToggled);
|
||||
var currentIndex = this.trackCollection.indexOf(model);
|
||||
var lastIndex = this.trackCollection.indexOf(lastToggled);
|
||||
|
||||
var low = Math.min(currentIndex, lastIndex);
|
||||
var high = Math.max(currentIndex, lastIndex);
|
||||
var range = _.range(low + 1, high);
|
||||
|
||||
this.episodeCollection.lastToggled = model;
|
||||
this.trackCollection.lastToggled = model;
|
||||
},
|
||||
|
||||
_updateEpisodeCollection : function() {
|
||||
_updateTrackCollection : function() {
|
||||
var self = this;
|
||||
|
||||
this.episodeCollection.add(this.fullEpisodeCollection.bySeason(this.model.get('seasonNumber')).models, { merge : true });
|
||||
this.trackCollection.add(this.fullTrackCollection.byAlbum(this.model.get('albumId')).models, { merge : true });
|
||||
|
||||
this.episodeCollection.each(function(model) {
|
||||
model.episodeCollection = self.episodeCollection;
|
||||
this.trackCollection.each(function(model) {
|
||||
model.trackCollection = self.trackCollection;
|
||||
});
|
||||
},
|
||||
|
||||
_refreshEpisodes : function() {
|
||||
this._updateEpisodeCollection();
|
||||
this.episodeCollection.fullCollection.sort();
|
||||
_updateImages : function () {
|
||||
var cover = this._getImage('cover');
|
||||
|
||||
if (cover) {
|
||||
this.ui.poster.attr('src', cover);
|
||||
}
|
||||
},
|
||||
|
||||
_refreshTracks : function() {
|
||||
this._updateTrackCollection();
|
||||
this.trackCollection.fullCollection.sort();
|
||||
this.render();
|
||||
},
|
||||
|
||||
_openEpisodeFileEditor : function() {
|
||||
var view = new EpisodeFileEditorLayout({
|
||||
model : this.model,
|
||||
series : this.series,
|
||||
episodeCollection : this.episodeCollection
|
||||
});
|
||||
_openTrackFileEditor : function() {
|
||||
//var view = new TrackFileEditorLayout({
|
||||
// model : this.model,
|
||||
// artist : this.artist,
|
||||
// trackCollection : this.trackCollection
|
||||
//});
|
||||
|
||||
vent.trigger(vent.Commands.OpenModalCommand, view);
|
||||
//vent.trigger(vent.Commands.OpenModalCommand, view);
|
||||
}
|
||||
});
|
|
@ -1,50 +1,68 @@
|
|||
<div class="artist-album" id="season-{{seasonNumber}}">
|
||||
<h2>
|
||||
<i class="x-album-monitored album-monitored clickable" title="Toggle album monitored status"/>
|
||||
|
||||
{{#if seasonNumber}}
|
||||
Season {{seasonNumber}}
|
||||
{{else}}
|
||||
Specials
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if_eq episodeCount compare=0}}
|
||||
{{#if monitored}}
|
||||
<span class="badge badge-primary album-status" title="No aired episodes"> </span>
|
||||
{{else}}
|
||||
<span class="badge badge-warning album-status" title="Album is not monitored"> </span>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#if_eq percentOfEpisodes compare=100}}
|
||||
<span class="badge badge-success album-status" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded">{{episodeFileCount}} / {{episodeCount}}</span>
|
||||
{{else}}
|
||||
<span class="badge badge-danger album-status" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded">{{episodeFileCount}} / {{episodeCount}}</span>
|
||||
{{/if_eq}}
|
||||
{{/if_eq}}
|
||||
|
||||
<span class="album-actions pull-right">
|
||||
<div class="x-album-episode-file-editor">
|
||||
<i class="icon-lidarr-episode-file" title="Modify episode files for album"/>
|
||||
</div>
|
||||
<div class="x-album-rename">
|
||||
<i class="icon-lidarr-rename" title="Preview rename for album {{seasonNumber}}"/>
|
||||
</div>
|
||||
<div class="x-album-search">
|
||||
<i class="icon-lidarr-search" title="Search for monitored episodes in album {{seasonNumber}}"/>
|
||||
</div>
|
||||
</span>
|
||||
</h2>
|
||||
<div class="show-hide-episodes x-show-hide-episodes">
|
||||
<h4>
|
||||
{{#if showingEpisodes}}
|
||||
<i class="icon-lidarr-panel-hide"/>
|
||||
Hide Episodes
|
||||
{{else}}
|
||||
<i class="icon-lidarr-panel-show"/>
|
||||
Show Episodes
|
||||
{{/if}}
|
||||
</h4>
|
||||
<div class="artist-album" id="album-{{title}}">
|
||||
<div class="visible-lg col-lg-2 poster">
|
||||
{{cover}}
|
||||
</div>
|
||||
<div class="x-episode-grid table-responsive"></div>
|
||||
<div class="col-md-12 col-lg-10">
|
||||
<h2 class="header-text">
|
||||
<i class="x-album-monitored album-monitored clickable" title="Toggle album monitored status"/>
|
||||
|
||||
{{#if title}}
|
||||
{{title}} ({{albumYear}})
|
||||
|
||||
{{else}}
|
||||
Specials
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#if_eq trackCount compare=0}}
|
||||
{{#if monitored}}
|
||||
<span class="badge badge-primary album-status" title="No aired tracks"> </span>
|
||||
{{else}}
|
||||
<span class="badge badge-warning album-status" title="Album is not monitored"> </span>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#if_eq percentOfTracks compare=100}}
|
||||
<span class="badge badge-success album-status" title="{{trackFileCount}}/{{trackCount}} tracks downloaded">{{trackFileCount}} / {{trackCount}} Tracks</span>
|
||||
{{else}}
|
||||
<span class="badge badge-danger album-status" title="{{trackFileCount}}/{{trackCount}} tracks downloaded">{{trackFileCount}} / {{trackCount}} Tracks</span>
|
||||
{{/if_eq}}
|
||||
{{/if_eq}}
|
||||
|
||||
<span class="album-actions pull-right">
|
||||
<div class="x-track-file-editor">
|
||||
<i class="icon-lidarr-track-file" title="Modify track files for album"/>
|
||||
</div>
|
||||
<div class="x-album-rename">
|
||||
<i class="icon-lidarr-rename" title="Preview rename for album {{albumId}}"/>
|
||||
</div>
|
||||
<div class="x-album-search">
|
||||
<i class="icon-lidarr-search" title="Search for monitored tracks in album {{albumId}}"/>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
</h2>
|
||||
|
||||
<div class="album-detail-release">
|
||||
Release Date: {{albumReleaseDate}}
|
||||
</div>
|
||||
<div class="album-detail-type">
|
||||
Type: Album
|
||||
</div>
|
||||
|
||||
<div id="album-info" class="album-info"></div>
|
||||
</div>
|
||||
|
||||
<div class="show-hide-tracks x-show-hide-tracks">
|
||||
<h4>
|
||||
{{#if showingTracks}}
|
||||
<i class="icon-lidarr-panel-hide"/>
|
||||
Hide Tracks
|
||||
{{else}}
|
||||
<i class="icon-lidarr-panel-show"/>
|
||||
Show Tracks
|
||||
{{/if}}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<div class="x-track-grid table-responsive"></div>
|
||||
</div>
|
||||
|
|
|
@ -36,7 +36,7 @@ module.exports = Marionette.Layout.extend({
|
|||
},
|
||||
|
||||
events : {
|
||||
'click .x-episode-file-editor' : '_openEpisodeFileEditor',
|
||||
'click .x-track-file-editor' : '_openTrackFileEditor',
|
||||
'click .x-monitored' : '_toggleMonitored',
|
||||
'click .x-edit' : '_editArtist',
|
||||
'click .x-refresh' : '_refreshArtist',
|
||||
|
@ -142,7 +142,7 @@ module.exports = Marionette.Layout.extend({
|
|||
_refreshArtist : function() {
|
||||
CommandController.Execute('refreshArtist', {
|
||||
name : 'refreshArtist',
|
||||
seriesId : this.model.id
|
||||
artistId : this.model.id
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -167,30 +167,19 @@ module.exports = Marionette.Layout.extend({
|
|||
|
||||
this.albums.show(new LoadingView());
|
||||
|
||||
this.albumCollection = new AlbumCollection(this.model.get('albums'));
|
||||
this.albumCollection = new AlbumCollection({ artistId : this.model.id }).bindSignalR();
|
||||
|
||||
this.trackCollection = new TrackCollection({ artistId : this.model.id }).bindSignalR();
|
||||
this.trackFileCollection = new TrackFileCollection({ artistId : this.model.id }).bindSignalR();
|
||||
|
||||
console.log (this.trackCollection);
|
||||
|
||||
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() {
|
||||
$.when(this.albumCollection.fetch(), this.trackCollection.fetch(), this.trackFileCollection.fetch()).done(function() {
|
||||
var albumCollectionView = new AlbumCollectionView({
|
||||
collection : self.albumCollection,
|
||||
trackCollection : self.trackCollection,
|
||||
|
@ -219,7 +208,7 @@ module.exports = Marionette.Layout.extend({
|
|||
},
|
||||
|
||||
_refresh : function() {
|
||||
this.albumCollection.add(this.model.get('albums'), { merge : true });
|
||||
this.albumCollection.fetch();
|
||||
this.trackCollection.fetch();
|
||||
this.trackFileCollection.fetch();
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{{name}}
|
||||
<div class="artist-actions pull-right">
|
||||
<div class="x-episode-file-editor">
|
||||
<i class="icon-lidarr-episode-file" title="Modify episode files for artist"/>
|
||||
<i class="icon-lidarr-track-file" title="Modify episode files for artist"/>
|
||||
</div>
|
||||
<div class="x-refresh">
|
||||
<i class="icon-lidarr-refresh icon-can-spin" title="Update artist info and scan disk"/>
|
||||
|
@ -32,4 +32,4 @@
|
|||
<div id="info" class="artist-info"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="seasons"></div>
|
||||
<div id="albums"></div>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<a href="{{discogsUrl}}" class="label label-info">Discogs</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if amId}}
|
||||
{{#if allMusicId}}
|
||||
<a href="{{allMusicUrl}}" class="label label-info">AllMusic</a>
|
||||
{{/if}}
|
||||
</span>
|
||||
|
|
|
@ -4,18 +4,14 @@ var reqres = require('../../reqres');
|
|||
var ArtistCollection = require('../ArtistCollection');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'episode-number-cell',
|
||||
className : 'track-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 artist = ArtistCollection.get(this.model.get('artistId'));
|
||||
|
||||
var alternateTitles = [];
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
var NzbDroneCell = require('../../Cells/NzbDroneCell');
|
||||
var ArtistCollection = require('../ArtistCollection');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'track-rating-cell',
|
||||
|
||||
|
||||
render : function() {
|
||||
this.$el.empty();
|
||||
var ratings = this.model.get('ratings')
|
||||
|
||||
if (ratings) {
|
||||
this.$el.html(ratings.value + ' (' + ratings.votes + ' votes)');
|
||||
}
|
||||
|
||||
this.delegateEvents();
|
||||
return this;
|
||||
}
|
||||
});
|
|
@ -9,7 +9,7 @@ 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 TrackProgressCell = require('../../Cells/TrackProgressCell');
|
||||
var ArtistActionsCell = require('../../Cells/ArtistActionsCell');
|
||||
var ArtistStatusCell = require('../../Cells/ArtistStatusCell');
|
||||
var FooterView = require('./FooterView');
|
||||
|
@ -62,10 +62,10 @@ module.exports = Marionette.Layout.extend({
|
|||
cell : RelativeDateCell
|
||||
},
|
||||
{
|
||||
name : 'percentOfEpisodes',
|
||||
name : 'percentOfTracks',
|
||||
label : 'Tracks',
|
||||
cell : EpisodeProgressCell,
|
||||
className : 'episode-progress-cell'
|
||||
cell : TrackProgressCell,
|
||||
className : 'track-progress-cell'
|
||||
},
|
||||
{
|
||||
name : 'this',
|
||||
|
@ -157,7 +157,7 @@ module.exports = Marionette.Layout.extend({
|
|||
},
|
||||
{
|
||||
title : 'Tracks',
|
||||
name : 'percentOfEpisodes'
|
||||
name : 'percentOfTracks'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -330,8 +330,8 @@ module.exports = Marionette.Layout.extend({
|
|||
|
||||
_.each(this.artistCollection.models, function(model) {
|
||||
albums += model.get('albumCount');
|
||||
tracks += model.get('episodeCount'); // TODO: Refactor to Seasons and Tracks
|
||||
trackFiles += model.get('episodeFileCount');
|
||||
tracks += model.get('trackCount'); // TODO: Refactor to Seasons and Tracks
|
||||
trackFiles += model.get('trackFileCount');
|
||||
|
||||
/*if (model.get('status').toLowerCase() === 'ended') {
|
||||
ended++;
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
{{profile profileId}}
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-4">
|
||||
{{> EpisodeProgressPartial }}
|
||||
{{> TrackProgressPartial }}
|
||||
</div>
|
||||
<div class="col-md-8 col-xs-10">
|
||||
Path {{path}}
|
||||
|
|
|
@ -21,7 +21,7 @@ module.exports = PageableCollection.extend({
|
|||
this.artistId = options.artistId;
|
||||
},
|
||||
|
||||
bySeason : function(album) {
|
||||
byAlbum : function(album) {
|
||||
var filtered = this.filter(function(track) {
|
||||
return track.get('albumId') === album;
|
||||
});
|
||||
|
@ -36,11 +36,11 @@ module.exports = PageableCollection.extend({
|
|||
var track2 = model2.get('trackNumber');
|
||||
|
||||
if (track1 < track2) {
|
||||
return 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (track1 > track2) {
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
.album-cover {
|
||||
min-width: 56px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.truncate {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
@ -22,6 +27,11 @@
|
|||
width : 168px;
|
||||
}
|
||||
|
||||
.album-cover {
|
||||
padding-left : 20px;
|
||||
width : 168px;
|
||||
}
|
||||
|
||||
.form-horizontal {
|
||||
margin-top : 10px;
|
||||
}
|
||||
|
@ -81,9 +91,9 @@
|
|||
.card;
|
||||
.opacity(0.9);
|
||||
margin : 30px 10px;
|
||||
padding : 10px 25px;
|
||||
padding : 30px 25px;
|
||||
|
||||
.show-hide-episodes {
|
||||
.show-hide-tracks {
|
||||
.clickable();
|
||||
text-align : center;
|
||||
|
||||
|
@ -91,6 +101,9 @@
|
|||
.clickable();
|
||||
}
|
||||
}
|
||||
.header-text {
|
||||
margin-top : 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.artist-posters {
|
||||
|
@ -261,39 +274,45 @@
|
|||
|
||||
.artist-album {
|
||||
|
||||
.episode-number-cell {
|
||||
.track-number-cell {
|
||||
width : 40px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.episode-air-date-cell {
|
||||
.track-air-date-cell {
|
||||
width : 150px;
|
||||
}
|
||||
|
||||
.episode-status-cell {
|
||||
.track-status-cell {
|
||||
width : 100px;
|
||||
}
|
||||
.track-rating-cell {
|
||||
width : 150px;
|
||||
}
|
||||
.track-explicit-cell {
|
||||
width : 100px;
|
||||
}
|
||||
|
||||
.episode-title-cell {
|
||||
.track-title-cell {
|
||||
cursor : pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.episode-detail-modal {
|
||||
.track-detail-modal {
|
||||
|
||||
.episode-info {
|
||||
.track-info {
|
||||
margin-bottom : 10px;
|
||||
}
|
||||
|
||||
.episode-overview {
|
||||
.track-overview {
|
||||
font-style : italic;
|
||||
}
|
||||
|
||||
.episode-file-info {
|
||||
.track-file-info {
|
||||
margin-top : 30px;
|
||||
font-size : 12px;
|
||||
}
|
||||
|
||||
.episode-history-details-cell .popover {
|
||||
.track-history-details-cell .popover {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
|
@ -302,7 +321,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.album-grid {
|
||||
.track-grid {
|
||||
.toggle-cell {
|
||||
width : 28px;
|
||||
text-align : center;
|
||||
|
@ -422,7 +441,7 @@
|
|||
//artist Details
|
||||
|
||||
.artist-not-monitored {
|
||||
.album-monitored, .episode-monitored {
|
||||
.album-monitored, .track-monitored {
|
||||
color: #888888;
|
||||
cursor: not-allowed;
|
||||
|
||||
|
@ -454,6 +473,28 @@
|
|||
}
|
||||
}
|
||||
|
||||
.album-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;
|
||||
}
|
||||
}
|
||||
|
||||
.album-info-links {
|
||||
@media (max-width: @screen-sm-max) {
|
||||
display : inline-block;
|
||||
margin-top : 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scene-info {
|
||||
.key, .value {
|
||||
display : inline-block;
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
{{> EpisodeProgressPartial }}
|
|
@ -0,0 +1,44 @@
|
|||
var vent = require('vent');
|
||||
var NzbDroneCell = require('./NzbDroneCell');
|
||||
var CommandController = require('../Commands/CommandController');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'track-actions-cell',
|
||||
|
||||
events : {
|
||||
'click .x-automatic-search' : '_automaticSearch',
|
||||
'click .x-manual-search' : '_manualSearch'
|
||||
},
|
||||
|
||||
render : function() {
|
||||
this.$el.empty();
|
||||
|
||||
this.$el.html('<i class="icon-lidarr-search x-automatic-search" title="Automatic Search"></i>' + '<i class="icon-lidarr-search-manual x-manual-search" title="Manual Search"></i>');
|
||||
|
||||
CommandController.bindToCommand({
|
||||
element : this.$el.find('.x-automatic-search'),
|
||||
command : {
|
||||
name : 'trackSearch',
|
||||
trackIds : [this.model.get('id')]
|
||||
}
|
||||
});
|
||||
|
||||
this.delegateEvents();
|
||||
return this;
|
||||
},
|
||||
|
||||
_automaticSearch : function() {
|
||||
CommandController.Execute('trackSearch', {
|
||||
name : 'trackSearch',
|
||||
trackIds : [this.model.get('id')]
|
||||
});
|
||||
},
|
||||
|
||||
_manualSearch : function() {
|
||||
vent.trigger(vent.Commands.ShowTrackDetails, {
|
||||
track : this.cellValue,
|
||||
hideSeriesLink : true,
|
||||
openingTab : 'search'
|
||||
});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
var vent = require('vent');
|
||||
var NzbDroneCell = require('./NzbDroneCell');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'track-explicit-cell',
|
||||
template : 'Cells/TrackExplicitCellTemplate',
|
||||
|
||||
render : function() {
|
||||
var explicit = this.cellValue.get('explicit');
|
||||
var print = '';
|
||||
|
||||
if (explicit === true) {
|
||||
print = 'Explicit';
|
||||
}
|
||||
|
||||
this.$el.html(print);
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
var _ = require('underscore');
|
||||
var ToggleCell = require('./ToggleCell');
|
||||
var ArtistCollection = require('../Artist/ArtistCollection');
|
||||
var Messenger = require('../Shared/Messenger');
|
||||
|
||||
module.exports = ToggleCell.extend({
|
||||
className : 'toggle-cell track-monitored',
|
||||
|
||||
_originalOnClick : ToggleCell.prototype._onClick,
|
||||
|
||||
_onClick : function(e) {
|
||||
|
||||
var artist = ArtistCollection.get(this.model.get('artistId'));
|
||||
|
||||
if (!artist.get('monitored')) {
|
||||
|
||||
Messenger.show({
|
||||
message : 'Unable to change monitored state when series is not monitored',
|
||||
type : 'error'
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.shiftKey && this.model.trackCollection.lastToggled) {
|
||||
this._selectRange();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._originalOnClick.apply(this, arguments);
|
||||
this.model.trackCollection.lastToggled = this.model;
|
||||
},
|
||||
|
||||
_selectRange : function() {
|
||||
var trackCollection = this.model.trackCollection;
|
||||
var lastToggled = trackCollection.lastToggled;
|
||||
|
||||
var currentIndex = trackCollection.indexOf(this.model);
|
||||
var lastIndex = trackCollection.indexOf(lastToggled);
|
||||
|
||||
var low = Math.min(currentIndex, lastIndex);
|
||||
var high = Math.max(currentIndex, lastIndex);
|
||||
var range = _.range(low + 1, high);
|
||||
|
||||
_.each(range, function(index) {
|
||||
var model = trackCollection.at(index);
|
||||
|
||||
model.set('monitored', lastToggled.get('monitored'));
|
||||
model.save();
|
||||
});
|
||||
|
||||
this.model.set('monitored', lastToggled.get('monitored'));
|
||||
this.model.save();
|
||||
this.model.trackCollection.lastToggled = undefined;
|
||||
}
|
||||
});
|
|
@ -2,21 +2,21 @@ var Marionette = require('marionette');
|
|||
var NzbDroneCell = require('./NzbDroneCell');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'episode-progress-cell',
|
||||
template : 'Cells/EpisodeProgressCellTemplate',
|
||||
className : 'track-progress-cell',
|
||||
template : 'Cells/TrackProgressCellTemplate',
|
||||
|
||||
render : function() {
|
||||
|
||||
var episodeCount = this.model.get('episodeCount');
|
||||
var episodeFileCount = this.model.get('episodeFileCount');
|
||||
var trackCount = this.model.get('trackCount');
|
||||
var trackFileCount = this.model.get('trackFileCount');
|
||||
|
||||
var percent = 100;
|
||||
|
||||
if (episodeCount > 0) {
|
||||
percent = episodeFileCount / episodeCount * 100;
|
||||
if (trackCount > 0) {
|
||||
percent = trackFileCount / trackCount * 100;
|
||||
}
|
||||
|
||||
this.model.set('percentOfEpisodes', percent);
|
||||
this.model.set('percentOfTracks', percent);
|
||||
|
||||
this.templateFunction = Marionette.TemplateCache.get(this.template);
|
||||
var data = this.model.toJSON();
|
|
@ -0,0 +1 @@
|
|||
{{> TrackProgressPartial }}
|
|
@ -0,0 +1,127 @@
|
|||
var reqres = require('../reqres');
|
||||
var Backbone = require('backbone');
|
||||
var NzbDroneCell = require('./NzbDroneCell');
|
||||
var QueueCollection = require('../Activity/Queue/QueueCollection');
|
||||
var moment = require('moment');
|
||||
var FormatHelpers = require('../Shared/FormatHelpers');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'track-status-cell',
|
||||
|
||||
render : function() {
|
||||
this.listenTo(QueueCollection, 'sync', this._renderCell);
|
||||
|
||||
this._renderCell();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_renderCell : function() {
|
||||
|
||||
if (this.trackFile) {
|
||||
this.stopListening(this.trackFile, 'change', this._refresh);
|
||||
}
|
||||
|
||||
this.$el.empty();
|
||||
|
||||
if (this.model) {
|
||||
|
||||
var icon;
|
||||
var tooltip;
|
||||
|
||||
var hasAired = moment(this.model.get('airDateUtc')).isBefore(moment());
|
||||
this.trackFile = this._getFile();
|
||||
|
||||
if (this.trackFile) {
|
||||
this.listenTo(this.trackFile, 'change', this._refresh);
|
||||
|
||||
var quality = this.trackFile.get('quality');
|
||||
var revision = quality.revision;
|
||||
var size = FormatHelpers.bytes(this.trackFile.get('size'));
|
||||
var title = 'Track downloaded';
|
||||
|
||||
if (revision.real && revision.real > 0) {
|
||||
title += '[REAL]';
|
||||
}
|
||||
|
||||
if (revision.version && revision.version > 1) {
|
||||
title += ' [PROPER]';
|
||||
}
|
||||
|
||||
if (size !== '') {
|
||||
title += ' - {0}'.format(size);
|
||||
}
|
||||
|
||||
if (this.trackFile.get('qualityCutoffNotMet')) {
|
||||
this.$el.html('<span class="badge badge-inverse" title="{0}">{1}</span>'.format(title, quality.quality.name));
|
||||
} else {
|
||||
this.$el.html('<span class="badge" title="{0}">{1}</span>'.format(title, quality.quality.name));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
else {
|
||||
var model = this.model;
|
||||
var downloading = false; //TODO Fix this by adding to QueueCollection
|
||||
|
||||
if (downloading) {
|
||||
var progress = 100 - (downloading.get('sizeleft') / downloading.get('size') * 100);
|
||||
|
||||
if (progress === 0) {
|
||||
icon = 'icon-lidarr-downloading';
|
||||
tooltip = 'Track is downloading';
|
||||
}
|
||||
|
||||
else {
|
||||
this.$el.html('<div class="progress" title="Track is downloading - {0}% {1}">'.format(progress.toFixed(1), downloading.get('title')) +
|
||||
'<div class="progress-bar progress-bar-purple" style="width: {0}%;"></div></div>'.format(progress));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
else if (this.model.get('grabbed')) {
|
||||
icon = 'icon-lidarr-downloading';
|
||||
tooltip = 'Track is downloading';
|
||||
}
|
||||
|
||||
else if (!this.model.get('airDateUtc')) {
|
||||
icon = 'icon-lidarr-tba';
|
||||
tooltip = 'TBA';
|
||||
}
|
||||
|
||||
else if (hasAired) {
|
||||
icon = 'icon-lidarr-missing';
|
||||
tooltip = 'Track missing from disk';
|
||||
} else {
|
||||
icon = 'icon-lidarr-not-aired';
|
||||
tooltip = 'Track has not aired';
|
||||
}
|
||||
}
|
||||
|
||||
this.$el.html('<i class="{0}" title="{1}"/>'.format(icon, tooltip));
|
||||
}
|
||||
},
|
||||
|
||||
_getFile : function() {
|
||||
var hasFile = this.model.get('hasFile');
|
||||
|
||||
if (hasFile) {
|
||||
var trackFile;
|
||||
|
||||
if (reqres.hasHandler(reqres.Requests.GetTrackFileById)) {
|
||||
trackFile = reqres.request(reqres.Requests.GetTrackFileById, this.model.get('trackFileId'));
|
||||
}
|
||||
|
||||
else if (this.model.has('trackFile')) {
|
||||
trackFile = new Backbone.Model(this.model.get('trackFile'));
|
||||
}
|
||||
|
||||
if (trackFile) {
|
||||
return trackFile;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
var vent = require('vent');
|
||||
var NzbDroneCell = require('./NzbDroneCell');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'track-title-cell',
|
||||
|
||||
events : {
|
||||
'click' : '_showDetails'
|
||||
},
|
||||
|
||||
render : function() {
|
||||
var title = this.cellValue.get('title');
|
||||
|
||||
if (!title || title === '') {
|
||||
title = 'TBA';
|
||||
}
|
||||
|
||||
this.$el.html(title);
|
||||
return this;
|
||||
},
|
||||
|
||||
_showDetails : function() {
|
||||
var hideArtistLink = this.column.get('hideArtistLink');
|
||||
vent.trigger(vent.Commands.ShowTrackDetails, {
|
||||
track : this.cellValue,
|
||||
hideArtistLink : hideArtistLink
|
||||
});
|
||||
}
|
||||
});
|
|
@ -15,7 +15,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.episode-title-cell {
|
||||
.track-title-cell {
|
||||
.text-overflow();
|
||||
|
||||
color: #428bca;
|
||||
|
@ -89,7 +89,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
td.episode-status-cell, td.quality-cell, td.history-quality-cell, td.progress-cell {
|
||||
td.track-status-cell, td.quality-cell, td.history-quality-cell, td.progress-cell {
|
||||
text-align: center;
|
||||
width: 80px;
|
||||
|
||||
|
@ -118,7 +118,7 @@ td.episode-status-cell, td.quality-cell, td.history-quality-cell, td.progress-ce
|
|||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.episode-actions-cell {
|
||||
.track-actions-cell {
|
||||
width: 55px;
|
||||
|
||||
i {
|
||||
|
@ -131,12 +131,12 @@ td.episode-status-cell, td.quality-cell, td.history-quality-cell, td.progress-ce
|
|||
}
|
||||
}
|
||||
|
||||
.episode-history-details-cell {
|
||||
.track-history-details-cell {
|
||||
width : 18px;
|
||||
}
|
||||
|
||||
.episode-detail-modal {
|
||||
.episode-actions-cell {
|
||||
.track-detail-modal {
|
||||
.track-actions-cell {
|
||||
width : 18px;
|
||||
}
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ td.episode-status-cell, td.quality-cell, td.history-quality-cell, td.progress-ce
|
|||
width : 80px;
|
||||
}
|
||||
|
||||
td.delete-episode-file-cell {
|
||||
td.delete-track-file-cell {
|
||||
.clickable();
|
||||
|
||||
text-align : center;
|
||||
|
@ -188,7 +188,7 @@ td.delete-episode-file-cell {
|
|||
width: 16px;
|
||||
}
|
||||
|
||||
.episode-number-cell {
|
||||
.track-number-cell {
|
||||
cursor : default;
|
||||
}
|
||||
|
||||
|
@ -198,7 +198,7 @@ td.delete-episode-file-cell {
|
|||
|
||||
.table>tbody>tr>td, .table>thead>tr>th {
|
||||
|
||||
&.episode-warning-cell {
|
||||
&.track-warning-cell {
|
||||
width : 1px;
|
||||
padding-left : 0px;
|
||||
padding-right : 0px;
|
||||
|
|
|
@ -496,7 +496,7 @@
|
|||
.fa-icon-content(@fa-var-retweet);
|
||||
}
|
||||
|
||||
.icon-lidarr-episode-file {
|
||||
.icon-lidarr-track-file {
|
||||
.fa-icon-content(@fa-var-file-video-o);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
@import "Bootstrap/variables";
|
||||
@import "variables";
|
||||
|
||||
.progress.episode-progress {
|
||||
.progress.track-progress {
|
||||
position : relative;
|
||||
margin-bottom : 2px;
|
||||
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
var Handlebars = require('handlebars');
|
||||
var StatusModel = require('../../System/StatusModel');
|
||||
var moment = require('moment');
|
||||
var _ = require('underscore');
|
||||
|
||||
Handlebars.registerHelper('cover', function() {
|
||||
|
||||
var placeholder = StatusModel.get('urlBase') + '/Content/Images/poster-dark.png';
|
||||
var cover = _.where(this.images, { coverType : 'cover' });
|
||||
|
||||
if (cover[0]) {
|
||||
if (!cover[0].url.match(/^https?:\/\//)) {
|
||||
return new Handlebars.SafeString('<img class="album-cover x-album-cover" {0}>'.format(Handlebars.helpers.defaultImg.call(null, cover[0].url, 250)));
|
||||
} else {
|
||||
var url = cover[0].url.replace(/^https?\:/, '');
|
||||
return new Handlebars.SafeString('<img class="album-cover x-album-cover" {0}>'.format(Handlebars.helpers.defaultImg.call(null, url)));
|
||||
}
|
||||
}
|
||||
|
||||
return new Handlebars.SafeString('<img class="album-cover placeholder-image" src="{0}">'.format(placeholder));
|
||||
});
|
||||
|
||||
|
||||
|
||||
Handlebars.registerHelper('MBAlbumUrl', function() {
|
||||
return 'https://musicbrainz.org/release-group/' + this.mbId;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('TADBAlbumUrl', function() {
|
||||
return 'http://www.theaudiodb.com/album/' + this.tadbId;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('discogsAlbumUrl', function() {
|
||||
return 'https://www.discogs.com/master/' + this.discogsId;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('allMusicAlbumUrl', function() {
|
||||
return 'http://www.allmusic.com/album/' + this.allMusicId;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('albumYear', function() {
|
||||
return new Handlebars.SafeString('<span class="year">{0}</span>'.format(moment(this.releaseDate).format('YYYY')));
|
||||
});
|
||||
Handlebars.registerHelper('albumReleaseDate', function() {
|
||||
return new Handlebars.SafeString('<span class="release">{0}</span>'.format(moment(this.releaseDate).format('L')));
|
||||
});
|
|
@ -9,16 +9,18 @@ Handlebars.registerHelper('poster', function() {
|
|||
|
||||
if (poster[0]) {
|
||||
if (!poster[0].url.match(/^https?:\/\//)) {
|
||||
return new Handlebars.SafeString('<img class="series-poster x-series-poster" {0}>'.format(Handlebars.helpers.defaultImg.call(null, poster[0].url, 250)));
|
||||
return new Handlebars.SafeString('<img class="artist-poster x-artist-poster" {0}>'.format(Handlebars.helpers.defaultImg.call(null, poster[0].url, 250)));
|
||||
} else {
|
||||
var url = poster[0].url.replace(/^https?\:/, '');
|
||||
return new Handlebars.SafeString('<img class="series-poster x-series-poster" {0}>'.format(Handlebars.helpers.defaultImg.call(null, url)));
|
||||
return new Handlebars.SafeString('<img class="artist-poster x-artist-poster" {0}>'.format(Handlebars.helpers.defaultImg.call(null, url)));
|
||||
}
|
||||
}
|
||||
|
||||
return new Handlebars.SafeString('<img class="series-poster placeholder-image" src="{0}">'.format(placeholder));
|
||||
return new Handlebars.SafeString('<img class="artist-poster placeholder-image" src="{0}">'.format(placeholder));
|
||||
});
|
||||
|
||||
|
||||
|
||||
Handlebars.registerHelper('MBUrl', function() {
|
||||
return 'https://musicbrainz.org/artist/' + this.mbId;
|
||||
});
|
||||
|
@ -32,25 +34,25 @@ Handlebars.registerHelper('discogsUrl', function() {
|
|||
});
|
||||
|
||||
Handlebars.registerHelper('allMusicUrl', function() {
|
||||
return 'http://www.allmusic.com/artist/' + this.amId;
|
||||
return 'http://www.allmusic.com/artist/' + this.allMusicId;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('route', function() {
|
||||
return StatusModel.get('urlBase') + '/artist/' + this.nameSlug;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('percentOfEpisodes', function() {
|
||||
var episodeCount = this.episodeCount;
|
||||
var episodeFileCount = this.episodeFileCount;
|
||||
// Handlebars.registerHelper('percentOfEpisodes', function() {
|
||||
// var episodeCount = this.episodeCount;
|
||||
// var episodeFileCount = this.episodeFileCount;
|
||||
|
||||
var percent = 100;
|
||||
// var percent = 100;
|
||||
|
||||
if (episodeCount > 0) {
|
||||
percent = episodeFileCount / episodeCount * 100;
|
||||
}
|
||||
// if (episodeCount > 0) {
|
||||
// percent = episodeFileCount / episodeCount * 100;
|
||||
// }
|
||||
|
||||
return percent;
|
||||
});
|
||||
// return percent;
|
||||
// });
|
||||
|
||||
Handlebars.registerHelper('percentOfTracks', function() {
|
||||
var trackCount = this.trackCount;
|
||||
|
|
|
@ -6,6 +6,7 @@ require('./Helpers/Numbers');
|
|||
require('./Helpers/Episode');
|
||||
//require('./Helpers/Series');
|
||||
require('./Helpers/Artist');
|
||||
require('./Helpers/Album');
|
||||
require('./Helpers/Quality');
|
||||
require('./Helpers/System');
|
||||
require('./Helpers/EachReverse');
|
||||
|
|
Loading…
Reference in New Issue