update subtitle interface

This commit is contained in:
Luke Pulverenti 2014-05-11 18:38:10 -04:00
parent 8bc41832e6
commit 0d605b8672
20 changed files with 253 additions and 63 deletions

View File

@ -0,0 +1,19 @@
using MediaBrowser.Model.Chapters;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Chapters
{
public class ChapterResponse
{
/// <summary>
/// Gets or sets the chapters.
/// </summary>
/// <value>The chapters.</value>
public List<RemoteChapterInfo> Chapters { get; set; }
public ChapterResponse()
{
Chapters = new List<RemoteChapterInfo>();
}
}
}

View File

@ -0,0 +1,29 @@
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Chapters
{
public class ChapterSearchRequest : IHasProviderIds
{
public string Language { get; set; }
public VideoContentType ContentType { get; set; }
public string MediaPath { get; set; }
public string SeriesName { get; set; }
public string Name { get; set; }
public int? IndexNumber { get; set; }
public int? IndexNumberEnd { get; set; }
public int? ParentIndexNumber { get; set; }
public int? ProductionYear { get; set; }
public long? RuntimeTicks { get; set; }
public Dictionary<string, string> ProviderIds { get; set; }
public ChapterSearchRequest()
{
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
}
}

View File

@ -0,0 +1,39 @@
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Chapters;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Chapters
{
public interface IChapterProvider
{
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
string Name { get; }
/// <summary>
/// Gets the supported media types.
/// </summary>
/// <value>The supported media types.</value>
IEnumerable<VideoContentType> SupportedMediaTypes { get; }
/// <summary>
/// Searches the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{RemoteChapterResult}}.</returns>
Task<IEnumerable<RemoteChapterResult>> Search(ChapterSearchRequest request, CancellationToken cancellationToken);
/// <summary>
/// Gets the chapters.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{ChapterResponse}.</returns>
Task<ChapterResponse> GetChapters(string id, CancellationToken cancellationToken);
}
}

View File

@ -77,6 +77,9 @@
<Compile Include="Channels\ChannelAudioItem.cs" /> <Compile Include="Channels\ChannelAudioItem.cs" />
<Compile Include="Channels\ChannelVideoItem.cs" /> <Compile Include="Channels\ChannelVideoItem.cs" />
<Compile Include="Channels\Channel.cs" /> <Compile Include="Channels\Channel.cs" />
<Compile Include="Chapters\ChapterSearchRequest.cs" />
<Compile Include="Chapters\IChapterProvider.cs" />
<Compile Include="Chapters\ChapterResponse.cs" />
<Compile Include="Collections\CollectionCreationOptions.cs" /> <Compile Include="Collections\CollectionCreationOptions.cs" />
<Compile Include="Collections\ICollectionManager.cs" /> <Compile Include="Collections\ICollectionManager.cs" />
<Compile Include="Dlna\ControlRequest.cs" /> <Compile Include="Dlna\ControlRequest.cs" />
@ -191,6 +194,7 @@
<Compile Include="Providers\IMetadataProvider.cs" /> <Compile Include="Providers\IMetadataProvider.cs" />
<Compile Include="Providers\IMetadataService.cs" /> <Compile Include="Providers\IMetadataService.cs" />
<Compile Include="Providers\IRemoteMetadataProvider.cs" /> <Compile Include="Providers\IRemoteMetadataProvider.cs" />
<Compile Include="Providers\VideoContentType.cs" />
<Compile Include="Security\IEncryptionManager.cs" /> <Compile Include="Security\IEncryptionManager.cs" />
<Compile Include="Subtitles\ISubtitleManager.cs" /> <Compile Include="Subtitles\ISubtitleManager.cs" />
<Compile Include="Subtitles\ISubtitleProvider.cs" /> <Compile Include="Subtitles\ISubtitleProvider.cs" />
@ -269,6 +273,8 @@
<Compile Include="Sorting\IUserBaseItemComparer.cs" /> <Compile Include="Sorting\IUserBaseItemComparer.cs" />
<Compile Include="Providers\BaseItemXmlParser.cs" /> <Compile Include="Providers\BaseItemXmlParser.cs" />
<Compile Include="Sorting\SortExtensions.cs" /> <Compile Include="Sorting\SortExtensions.cs" />
<Compile Include="Subtitles\SubtitleResponse.cs" />
<Compile Include="Subtitles\SubtitleSearchRequest.cs" />
<Compile Include="Themes\IAppThemeManager.cs" /> <Compile Include="Themes\IAppThemeManager.cs" />
<Compile Include="Themes\InternalThemeImage.cs" /> <Compile Include="Themes\InternalThemeImage.cs" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,19 @@

namespace MediaBrowser.Controller.Providers
{
/// <summary>
/// Enum VideoContentType
/// </summary>
public enum VideoContentType
{
/// <summary>
/// The episode
/// </summary>
Episode = 0,
/// <summary>
/// The movie
/// </summary>
Movie = 1
}
}

View File

@ -1,8 +1,6 @@
using MediaBrowser.Model.Entities; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -20,7 +18,7 @@ namespace MediaBrowser.Controller.Subtitles
/// Gets the supported media types. /// Gets the supported media types.
/// </summary> /// </summary>
/// <value>The supported media types.</value> /// <value>The supported media types.</value>
IEnumerable<SubtitleMediaType> SupportedMediaTypes { get; } IEnumerable<VideoContentType> SupportedMediaTypes { get; }
/// <summary> /// <summary>
/// Searches the subtitles. /// Searches the subtitles.
@ -28,7 +26,7 @@ namespace MediaBrowser.Controller.Subtitles
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{RemoteSubtitleInfo}}.</returns> /// <returns>Task{IEnumerable{RemoteSubtitleInfo}}.</returns>
Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken); Task<IEnumerable<RemoteSubtitleInfo>> Search(SubtitleSearchRequest request, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Gets the subtitles. /// Gets the subtitles.
@ -38,38 +36,4 @@ namespace MediaBrowser.Controller.Subtitles
/// <returns>Task{SubtitleResponse}.</returns> /// <returns>Task{SubtitleResponse}.</returns>
Task<SubtitleResponse> GetSubtitles(string id, CancellationToken cancellationToken); Task<SubtitleResponse> GetSubtitles(string id, CancellationToken cancellationToken);
} }
public enum SubtitleMediaType
{
Episode = 0,
Movie = 1
}
public class SubtitleResponse
{
public string Language { get; set; }
public string Format { get; set; }
public Stream Stream { get; set; }
}
public class SubtitleSearchRequest : IHasProviderIds
{
public string Language { get; set; }
public SubtitleMediaType ContentType { get; set; }
public string MediaPath { get; set; }
public string SeriesName { get; set; }
public string Name { get; set; }
public int? IndexNumber { get; set; }
public int? IndexNumberEnd { get; set; }
public int? ParentIndexNumber { get; set; }
public int? ProductionYear { get; set; }
public Dictionary<string, string> ProviderIds { get; set; }
public SubtitleSearchRequest()
{
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
}
} }

View File

@ -0,0 +1,11 @@
using System.IO;
namespace MediaBrowser.Controller.Subtitles
{
public class SubtitleResponse
{
public string Language { get; set; }
public string Format { get; set; }
public Stream Stream { get; set; }
}
}

View File

@ -0,0 +1,29 @@
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Subtitles
{
public class SubtitleSearchRequest : IHasProviderIds
{
public string Language { get; set; }
public VideoContentType ContentType { get; set; }
public string MediaPath { get; set; }
public string SeriesName { get; set; }
public string Name { get; set; }
public int? IndexNumber { get; set; }
public int? IndexNumberEnd { get; set; }
public int? ParentIndexNumber { get; set; }
public int? ProductionYear { get; set; }
public long? RuntimeTicks { get; set; }
public Dictionary<string, string> ProviderIds { get; set; }
public SubtitleSearchRequest()
{
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
}
}

View File

@ -83,6 +83,12 @@
<Compile Include="..\MediaBrowser.Model\Channels\ChannelQuery.cs"> <Compile Include="..\MediaBrowser.Model\Channels\ChannelQuery.cs">
<Link>Channels\ChannelQuery.cs</Link> <Link>Channels\ChannelQuery.cs</Link>
</Compile> </Compile>
<Compile Include="..\MediaBrowser.Model\Chapters\RemoteChapterInfo.cs">
<Link>Chapters\RemoteChapterInfo.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Chapters\RemoteChapterResult.cs">
<Link>Chapters\RemoteChapterResult.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs"> <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
<Link>Configuration\BaseApplicationConfiguration.cs</Link> <Link>Configuration\BaseApplicationConfiguration.cs</Link>
</Compile> </Compile>

View File

@ -70,6 +70,12 @@
<Compile Include="..\MediaBrowser.Model\Channels\ChannelQuery.cs"> <Compile Include="..\MediaBrowser.Model\Channels\ChannelQuery.cs">
<Link>Channels\ChannelQuery.cs</Link> <Link>Channels\ChannelQuery.cs</Link>
</Compile> </Compile>
<Compile Include="..\MediaBrowser.Model\Chapters\RemoteChapterInfo.cs">
<Link>Chapters\RemoteChapterInfo.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Chapters\RemoteChapterResult.cs">
<Link>Chapters\RemoteChapterResult.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs"> <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
<Link>Configuration\BaseApplicationConfiguration.cs</Link> <Link>Configuration\BaseApplicationConfiguration.cs</Link>
</Compile> </Compile>

View File

@ -0,0 +1,18 @@

namespace MediaBrowser.Model.Chapters
{
public class RemoteChapterInfo
{
/// <summary>
/// Gets or sets the start position ticks.
/// </summary>
/// <value>The start position ticks.</value>
public long StartPositionTicks { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
}
}

View File

@ -0,0 +1,36 @@

namespace MediaBrowser.Model.Chapters
{
public class RemoteChapterResult
{
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>The identifier.</value>
public string Id { get; set; }
/// <summary>
/// Gets or sets the run time ticks.
/// </summary>
/// <value>The run time ticks.</value>
public long? RunTimeTicks { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the community rating.
/// </summary>
/// <value>The community rating.</value>
public float? CommunityRating { get; set; }
/// <summary>
/// Gets or sets the chapter count.
/// </summary>
/// <value>The chapter count.</value>
public int? ChapterCount { get; set; }
}
}

View File

@ -1,5 +1,4 @@
using System; using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.Serialization; using System.Runtime.Serialization;
@ -17,6 +16,12 @@ namespace MediaBrowser.Model.Dto
/// <value>The name.</value> /// <value>The name.</value>
public string Name { get; set; } public string Name { get; set; }
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>The identifier.</value>
public string Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the role. /// Gets or sets the role.
/// </summary> /// </summary>

View File

@ -61,6 +61,8 @@
<Compile Include="ApiClient\SessionUpdatesEventArgs.cs" /> <Compile Include="ApiClient\SessionUpdatesEventArgs.cs" />
<Compile Include="Channels\ChannelItemQuery.cs" /> <Compile Include="Channels\ChannelItemQuery.cs" />
<Compile Include="Channels\ChannelQuery.cs" /> <Compile Include="Channels\ChannelQuery.cs" />
<Compile Include="Chapters\RemoteChapterInfo.cs" />
<Compile Include="Chapters\RemoteChapterResult.cs" />
<Compile Include="Configuration\TvFileOrganizationOptions.cs" /> <Compile Include="Configuration\TvFileOrganizationOptions.cs" />
<Compile Include="Configuration\BaseApplicationConfiguration.cs" /> <Compile Include="Configuration\BaseApplicationConfiguration.cs" />
<Compile Include="Configuration\DlnaOptions.cs" /> <Compile Include="Configuration\DlnaOptions.cs" />

View File

@ -1,6 +1,7 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
@ -37,15 +38,15 @@ namespace MediaBrowser.Providers.MediaInfo
return new List<string>(); return new List<string>();
} }
SubtitleMediaType mediaType; VideoContentType mediaType;
if (video is Episode) if (video is Episode)
{ {
mediaType = SubtitleMediaType.Episode; mediaType = VideoContentType.Episode;
} }
else if (video is Movie) else if (video is Movie)
{ {
mediaType = SubtitleMediaType.Movie; mediaType = VideoContentType.Movie;
} }
else else
{ {
@ -82,7 +83,7 @@ namespace MediaBrowser.Providers.MediaInfo
bool skipIfGraphicalSubtitlesPresent, bool skipIfGraphicalSubtitlesPresent,
bool skipIfAudioTrackMatches, bool skipIfAudioTrackMatches,
string language, string language,
SubtitleMediaType mediaType, VideoContentType mediaType,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
// There's already subtitles for this language // There's already subtitles for this language

View File

@ -1,6 +1,7 @@
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
@ -72,17 +73,17 @@ namespace MediaBrowser.Providers.Subtitles
get { return "Open Subtitles"; } get { return "Open Subtitles"; }
} }
public IEnumerable<SubtitleMediaType> SupportedMediaTypes public IEnumerable<VideoContentType> SupportedMediaTypes
{ {
get get
{ {
if (string.IsNullOrWhiteSpace(_config.Configuration.SubtitleOptions.OpenSubtitlesUsername) || if (string.IsNullOrWhiteSpace(_config.Configuration.SubtitleOptions.OpenSubtitlesUsername) ||
string.IsNullOrWhiteSpace(_config.Configuration.SubtitleOptions.OpenSubtitlesPasswordHash)) string.IsNullOrWhiteSpace(_config.Configuration.SubtitleOptions.OpenSubtitlesPasswordHash))
{ {
return new SubtitleMediaType[] { }; return new VideoContentType[] { };
} }
return new[] { SubtitleMediaType.Episode, SubtitleMediaType.Movie }; return new[] { VideoContentType.Episode, VideoContentType.Movie };
} }
} }
@ -163,21 +164,21 @@ namespace MediaBrowser.Providers.Subtitles
_lastLogin = DateTime.UtcNow; _lastLogin = DateTime.UtcNow;
} }
public async Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteSubtitleInfo>> Search(SubtitleSearchRequest request, CancellationToken cancellationToken)
{ {
var imdbIdText = request.GetProviderId(MetadataProviders.Imdb); var imdbIdText = request.GetProviderId(MetadataProviders.Imdb);
long imdbId = 0; long imdbId = 0;
switch (request.ContentType) switch (request.ContentType)
{ {
case SubtitleMediaType.Episode: case VideoContentType.Episode:
if (!request.IndexNumber.HasValue || !request.ParentIndexNumber.HasValue || string.IsNullOrEmpty(request.SeriesName)) if (!request.IndexNumber.HasValue || !request.ParentIndexNumber.HasValue || string.IsNullOrEmpty(request.SeriesName))
{ {
_logger.Debug("Episode information missing"); _logger.Debug("Episode information missing");
return new List<RemoteSubtitleInfo>(); return new List<RemoteSubtitleInfo>();
} }
break; break;
case SubtitleMediaType.Movie: case VideoContentType.Movie:
if (string.IsNullOrEmpty(request.Name)) if (string.IsNullOrEmpty(request.Name))
{ {
_logger.Debug("Movie name missing"); _logger.Debug("Movie name missing");
@ -206,8 +207,8 @@ namespace MediaBrowser.Providers.Subtitles
var hash = Utilities.ComputeHash(request.MediaPath); var hash = Utilities.ComputeHash(request.MediaPath);
var fileInfo = new FileInfo(request.MediaPath); var fileInfo = new FileInfo(request.MediaPath);
var movieByteSize = fileInfo.Length; var movieByteSize = fileInfo.Length;
var searchImdbId = request.ContentType == SubtitleMediaType.Movie ? imdbId.ToString(_usCulture) : ""; var searchImdbId = request.ContentType == VideoContentType.Movie ? imdbId.ToString(_usCulture) : "";
var subtitleSearchParameters = request.ContentType == SubtitleMediaType.Episode var subtitleSearchParameters = request.ContentType == VideoContentType.Episode
? new List<SubtitleSearchParameters> { ? new List<SubtitleSearchParameters> {
new SubtitleSearchParameters(subLanguageId, new SubtitleSearchParameters(subLanguageId,
query: request.SeriesName, query: request.SeriesName,
@ -234,7 +235,7 @@ namespace MediaBrowser.Providers.Subtitles
Predicate<SubtitleSearchResult> mediaFilter = Predicate<SubtitleSearchResult> mediaFilter =
x => x =>
request.ContentType == SubtitleMediaType.Episode request.ContentType == VideoContentType.Episode
? !string.IsNullOrEmpty(x.SeriesSeason) && !string.IsNullOrEmpty(x.SeriesEpisode) && ? !string.IsNullOrEmpty(x.SeriesSeason) && !string.IsNullOrEmpty(x.SeriesEpisode) &&
int.Parse(x.SeriesSeason, _usCulture) == request.ParentIndexNumber && int.Parse(x.SeriesSeason, _usCulture) == request.ParentIndexNumber &&
int.Parse(x.SeriesEpisode, _usCulture) == request.IndexNumber int.Parse(x.SeriesEpisode, _usCulture) == request.IndexNumber

View File

@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
@ -45,7 +46,7 @@ namespace MediaBrowser.Providers.Subtitles
{ {
try try
{ {
return await i.SearchSubtitles(request, cancellationToken).ConfigureAwait(false); return await i.Search(request, cancellationToken).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -99,15 +100,15 @@ namespace MediaBrowser.Providers.Subtitles
return Task.FromResult<IEnumerable<RemoteSubtitleInfo>>(new List<RemoteSubtitleInfo>()); return Task.FromResult<IEnumerable<RemoteSubtitleInfo>>(new List<RemoteSubtitleInfo>());
} }
SubtitleMediaType mediaType; VideoContentType mediaType;
if (video is Episode) if (video is Episode)
{ {
mediaType = SubtitleMediaType.Episode; mediaType = VideoContentType.Episode;
} }
else if (video is Movie) else if (video is Movie)
{ {
mediaType = SubtitleMediaType.Movie; mediaType = VideoContentType.Movie;
} }
else else
{ {
@ -124,7 +125,8 @@ namespace MediaBrowser.Providers.Subtitles
Name = video.Name, Name = video.Name,
ParentIndexNumber = video.ParentIndexNumber, ParentIndexNumber = video.ParentIndexNumber,
ProductionYear = video.ProductionYear, ProductionYear = video.ProductionYear,
ProviderIds = video.ProviderIds ProviderIds = video.ProviderIds,
RuntimeTicks = video.RunTimeTicks
}; };
var episode = video as Episode; var episode = video as Episode;

View File

@ -429,6 +429,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (dictionary.TryGetValue(person.Name, out entity)) if (dictionary.TryGetValue(person.Name, out entity))
{ {
baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary); baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary);
baseItemPerson.Id = entity.Id.ToString("N");
} }
dto.People[i] = baseItemPerson; dto.People[i] = baseItemPerson;

View File

@ -517,7 +517,6 @@ namespace MediaBrowser.WebDashboard.Api
"librarymenu.js", "librarymenu.js",
"mediacontroller.js", "mediacontroller.js",
"chromecast.js", "chromecast.js",
"contextmenu.js",
"backdrops.js", "backdrops.js",
"mediaplayer.js", "mediaplayer.js",

View File

@ -592,9 +592,6 @@
<Content Include="dashboard-ui\scripts\chromecast.js"> <Content Include="dashboard-ui\scripts\chromecast.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\scripts\contextmenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\dashboardgeneral.js"> <Content Include="dashboard-ui\scripts\dashboardgeneral.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>