aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.hgignore2
-rw-r--r--MediaBrowser.Api/Drawing/DrawingUtils.cs81
-rw-r--r--MediaBrowser.Api/Drawing/ImageProcessor.cs148
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj2
-rw-r--r--MediaBrowser.Common/Kernel/BaseKernel.cs15
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj1
-rw-r--r--MediaBrowser.Common/Mef/MefUtils.cs43
-rw-r--r--MediaBrowser.Common/Plugins/BasePlugin.cs3
-rw-r--r--MediaBrowser.Common/Plugins/BaseTheme.cs66
-rw-r--r--MediaBrowser.Controller/Kernel.cs1
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/Converters/TileBackgroundConverter.cs43
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/Converters/WeatherImageConverter.cs43
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/MediaBrowser.Plugins.DefaultTheme.csproj30
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/Pages/LoginPage.xaml64
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/Pages/LoginPage.xaml.cs15
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/Plugin.cs5
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/Resources/AppResources.cs14
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/Resources/AppResources.xaml81
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/Resources/Images/CurrentUserDefault.pngbin0 -> 968 bytes
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/Resources/Images/UserLoginDefault.pngbin0 -> 3179 bytes
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Overcast.pngbin0 -> 1578 bytes
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Rain.pngbin0 -> 1147 bytes
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Snow.pngbin0 -> 1458 bytes
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Sunny.pngbin0 -> 1302 bytes
-rw-r--r--MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Thunder.pngbin0 -> 1166 bytes
-rw-r--r--MediaBrowser.ServerApplication/App.config2
-rw-r--r--MediaBrowser.UI.sln (renamed from MediaBrowser.Plugins.sln)42
-rw-r--r--MediaBrowser.UI/App.config9
-rw-r--r--MediaBrowser.UI/App.xaml14
-rw-r--r--MediaBrowser.UI/App.xaml.cs213
-rw-r--r--MediaBrowser.UI/Configuration/UIApplicationConfiguration.cs27
-rw-r--r--MediaBrowser.UI/Configuration/UIApplicationPaths.cs8
-rw-r--r--MediaBrowser.UI/Controller/PluginUpdater.cs231
-rw-r--r--MediaBrowser.UI/Controller/UIKernel.cs97
-rw-r--r--MediaBrowser.UI/Controls/EnhancedScrollViewer.cs73
-rw-r--r--MediaBrowser.UI/Controls/ExtendedImage.cs92
-rw-r--r--MediaBrowser.UI/Controls/TreeHelper.cs226
-rw-r--r--MediaBrowser.UI/Controls/WindowCommands.xaml91
-rw-r--r--MediaBrowser.UI/Controls/WindowCommands.xaml.cs50
-rw-r--r--MediaBrowser.UI/Converters/CurrentUserVisibilityConverter.cs26
-rw-r--r--MediaBrowser.UI/Converters/DateTimeToStringConverter.cs34
-rw-r--r--MediaBrowser.UI/Converters/LastSeenTextConverter.cs86
-rw-r--r--MediaBrowser.UI/Converters/UserImageConverter.cs60
-rw-r--r--MediaBrowser.UI/Converters/WeatherTemperatureConverter.cs31
-rw-r--r--MediaBrowser.UI/Converters/WeatherVisibilityConverter.cs20
-rw-r--r--MediaBrowser.UI/MainWindow.xaml50
-rw-r--r--MediaBrowser.UI/MainWindow.xaml.cs368
-rw-r--r--MediaBrowser.UI/MediaBrowser.UI.csproj196
-rw-r--r--MediaBrowser.UI/Pages/BaseLoginPage.cs33
-rw-r--r--MediaBrowser.UI/Pages/BasePage.cs79
-rw-r--r--MediaBrowser.UI/Properties/AssemblyInfo.cs53
-rw-r--r--MediaBrowser.UI/Properties/Resources.Designer.cs71
-rw-r--r--MediaBrowser.UI/Properties/Resources.resx117
-rw-r--r--MediaBrowser.UI/Properties/Settings.Designer.cs30
-rw-r--r--MediaBrowser.UI/Properties/Settings.settings7
-rw-r--r--MediaBrowser.UI/Resources/AppResources.xaml122
-rw-r--r--MediaBrowser.UI/Resources/Images/BackButton.pngbin0 -> 1461 bytes
-rw-r--r--MediaBrowser.UI/Resources/Images/ExitButton.pngbin0 -> 1486 bytes
-rw-r--r--MediaBrowser.UI/Resources/Images/ForwardButton.pngbin0 -> 1442 bytes
-rw-r--r--MediaBrowser.UI/Resources/Images/Icon.icobin0 -> 32038 bytes
-rw-r--r--MediaBrowser.UI/Resources/Images/MuteButton.pngbin0 -> 1550 bytes
-rw-r--r--MediaBrowser.UI/Resources/Images/SettingsButton.pngbin0 -> 1690 bytes
-rw-r--r--MediaBrowser.UI/Resources/Images/VolumeDownButton.pngbin0 -> 1363 bytes
-rw-r--r--MediaBrowser.UI/Resources/Images/VolumeUpButton.pngbin0 -> 1450 bytes
-rw-r--r--MediaBrowser.UI/Resources/Images/mblogoblack.pngbin0 -> 32983 bytes
-rw-r--r--MediaBrowser.UI/Resources/Images/mblogowhite.pngbin0 -> 27029 bytes
-rw-r--r--MediaBrowser.UI/Resources/MainWindowResources.xaml43
-rw-r--r--MediaBrowser.UI/Resources/NavBarResources.xaml122
-rw-r--r--MediaBrowser.UI/Themes/Generic.xaml32
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj2
70 files changed, 3384 insertions, 30 deletions
diff --git a/.hgignore b/.hgignore
index 6fd079851..c8162e4c8 100644
--- a/.hgignore
+++ b/.hgignore
@@ -29,6 +29,8 @@ syntax: glob
obj/
[Rr]elease*/
ProgramData*/
+ProgramData-Server*/
+ProgramData-UI*/
_ReSharper*/
[Tt]humbs.db
[Tt]est[Rr]esult*
diff --git a/MediaBrowser.Api/Drawing/DrawingUtils.cs b/MediaBrowser.Api/Drawing/DrawingUtils.cs
new file mode 100644
index 000000000..f76a74218
--- /dev/null
+++ b/MediaBrowser.Api/Drawing/DrawingUtils.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Drawing;
+
+namespace MediaBrowser.Api.Drawing
+{
+ public static class DrawingUtils
+ {
+ /// <summary>
+ /// Resizes a set of dimensions
+ /// </summary>
+ public static Size Resize(int currentWidth, int currentHeight, int? width, int? height, int? maxWidth, int? maxHeight)
+ {
+ return Resize(new Size(currentWidth, currentHeight), width, height, maxWidth, maxHeight);
+ }
+
+ /// <summary>
+ /// Resizes a set of dimensions
+ /// </summary>
+ /// <param name="size">The original size object</param>
+ /// <param name="width">A new fixed width, if desired</param>
+ /// <param name="height">A new fixed neight, if desired</param>
+ /// <param name="maxWidth">A max fixed width, if desired</param>
+ /// <param name="maxHeight">A max fixed height, if desired</param>
+ /// <returns>A new size object</returns>
+ public static Size Resize(Size size, int? width, int? height, int? maxWidth, int? maxHeight)
+ {
+ decimal newWidth = size.Width;
+ decimal newHeight = size.Height;
+
+ if (width.HasValue && height.HasValue)
+ {
+ newWidth = width.Value;
+ newHeight = height.Value;
+ }
+
+ else if (height.HasValue)
+ {
+ newWidth = GetNewWidth(newHeight, newWidth, height.Value);
+ newHeight = height.Value;
+ }
+
+ else if (width.HasValue)
+ {
+ newHeight = GetNewHeight(newHeight, newWidth, width.Value);
+ newWidth = width.Value;
+ }
+
+ if (maxHeight.HasValue && maxHeight < newHeight)
+ {
+ newWidth = GetNewWidth(newHeight, newWidth, maxHeight.Value);
+ newHeight = maxHeight.Value;
+ }
+
+ if (maxWidth.HasValue && maxWidth < newWidth)
+ {
+ newHeight = GetNewHeight(newHeight, newWidth, maxWidth.Value);
+ newWidth = maxWidth.Value;
+ }
+
+ return new Size(Convert.ToInt32(newWidth), Convert.ToInt32(newHeight));
+ }
+
+ private static decimal GetNewWidth(decimal currentHeight, decimal currentWidth, int newHeight)
+ {
+ decimal scaleFactor = newHeight;
+ scaleFactor /= currentHeight;
+ scaleFactor *= currentWidth;
+
+ return scaleFactor;
+ }
+
+ private static decimal GetNewHeight(decimal currentHeight, decimal currentWidth, int newWidth)
+ {
+ decimal scaleFactor = newWidth;
+ scaleFactor /= currentWidth;
+ scaleFactor *= currentHeight;
+
+ return scaleFactor;
+ }
+ }
+}
diff --git a/MediaBrowser.Api/Drawing/ImageProcessor.cs b/MediaBrowser.Api/Drawing/ImageProcessor.cs
new file mode 100644
index 000000000..1a471acf5
--- /dev/null
+++ b/MediaBrowser.Api/Drawing/ImageProcessor.cs
@@ -0,0 +1,148 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Api.Drawing
+{
+ public static class ImageProcessor
+ {
+ /// <summary>
+ /// Processes an image by resizing to target dimensions
+ /// </summary>
+ /// <param name="entity">The entity that owns the image</param>
+ /// <param name="imageType">The image type</param>
+ /// <param name="imageIndex">The image index (currently only used with backdrops)</param>
+ /// <param name="toStream">The stream to save the new image to</param>
+ /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
+ /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
+ /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
+ /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
+ /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
+ public static void ProcessImage(BaseEntity entity, ImageType imageType, int imageIndex, Stream toStream, int? width, int? height, int? maxWidth, int? maxHeight, int? quality)
+ {
+ Image originalImage = Image.FromFile(GetImagePath(entity, imageType, imageIndex));
+
+ // Determine the output size based on incoming parameters
+ Size newSize = DrawingUtils.Resize(originalImage.Size, width, height, maxWidth, maxHeight);
+
+ Bitmap thumbnail;
+
+ // Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
+ if (originalImage.PixelFormat.HasFlag(PixelFormat.Indexed))
+ {
+ thumbnail = new Bitmap(originalImage, newSize.Width, newSize.Height);
+ }
+ else
+ {
+ thumbnail = new Bitmap(newSize.Width, newSize.Height, originalImage.PixelFormat);
+ }
+
+ thumbnail.MakeTransparent();
+
+ // Preserve the original resolution
+ thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
+
+ Graphics thumbnailGraph = Graphics.FromImage(thumbnail);
+
+ thumbnailGraph.CompositingQuality = CompositingQuality.HighQuality;
+ thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
+ thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
+ thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
+ thumbnailGraph.CompositingMode = CompositingMode.SourceOver;
+
+ thumbnailGraph.DrawImage(originalImage, 0, 0, newSize.Width, newSize.Height);
+
+ ImageFormat outputFormat = originalImage.RawFormat;
+
+ // Write to the output stream
+ SaveImage(outputFormat, thumbnail, toStream, quality);
+
+ thumbnailGraph.Dispose();
+ thumbnail.Dispose();
+ originalImage.Dispose();
+ }
+
+ public static string GetImagePath(BaseEntity entity, ImageType imageType, int imageIndex)
+ {
+ var item = entity as BaseItem;
+
+ if (item != null)
+ {
+ if (imageType == ImageType.Logo)
+ {
+ return item.LogoImagePath;
+ }
+ if (imageType == ImageType.Backdrop)
+ {
+ return item.BackdropImagePaths.ElementAt(imageIndex);
+ }
+ if (imageType == ImageType.Banner)
+ {
+ return item.BannerImagePath;
+ }
+ if (imageType == ImageType.Art)
+ {
+ return item.ArtImagePath;
+ }
+ if (imageType == ImageType.Thumbnail)
+ {
+ return item.ThumbnailImagePath;
+ }
+ }
+
+ return entity.PrimaryImagePath;
+ }
+
+ public static void SaveImage(ImageFormat outputFormat, Image newImage, Stream toStream, int? quality)
+ {
+ // Use special save methods for jpeg and png that will result in a much higher quality image
+ // All other formats use the generic Image.Save
+ if (ImageFormat.Jpeg.Equals(outputFormat))
+ {
+ SaveJpeg(newImage, toStream, quality);
+ }
+ else if (ImageFormat.Png.Equals(outputFormat))
+ {
+ newImage.Save(toStream, ImageFormat.Png);
+ }
+ else
+ {
+ newImage.Save(toStream, outputFormat);
+ }
+ }
+
+ public static void SaveJpeg(Image image, Stream target, int? quality)
+ {
+ if (!quality.HasValue)
+ {
+ quality = 90;
+ }
+
+ using (var encoderParameters = new EncoderParameters(1))
+ {
+ encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality.Value);
+ image.Save(target, GetImageCodecInfo("image/jpeg"), encoderParameters);
+ }
+ }
+
+ public static ImageCodecInfo GetImageCodecInfo(string mimeType)
+ {
+ ImageCodecInfo[] info = ImageCodecInfo.GetImageEncoders();
+
+ for (int i = 0; i < info.Length; i++)
+ {
+ ImageCodecInfo ici = info[i];
+ if (ici.MimeType.Equals(mimeType, StringComparison.OrdinalIgnoreCase))
+ {
+ return ici;
+ }
+ }
+ return info[1];
+ }
+ }
+}
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index 44b58852b..1af7e71bd 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -105,7 +105,7 @@
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
- <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\" /y</PostBuildEvent>
+ <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData-Server\Plugins\" /y</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
diff --git a/MediaBrowser.Common/Kernel/BaseKernel.cs b/MediaBrowser.Common/Kernel/BaseKernel.cs
index 5a0e1c5e5..a6081a688 100644
--- a/MediaBrowser.Common/Kernel/BaseKernel.cs
+++ b/MediaBrowser.Common/Kernel/BaseKernel.cs
@@ -1,5 +1,6 @@
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Logging;
+using MediaBrowser.Common.Mef;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Common.Plugins;
@@ -10,6 +11,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
+using System.ComponentModel.Composition.Primitives;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -88,6 +90,9 @@ namespace MediaBrowser.Common.Kernel
/// </summary>
private IDisposable HttpListener { get; set; }
+ /// <summary>
+ /// Gets the MEF CompositionContainer
+ /// </summary>
private CompositionContainer CompositionContainer { get; set; }
protected virtual string HttpServerUrlPrefix
@@ -184,18 +189,20 @@ namespace MediaBrowser.Common.Kernel
// This will prevent the .dll file from getting locked, and allow us to replace it when needed
IEnumerable<Assembly> pluginAssemblies = Directory.GetFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly).Select(f => Assembly.Load(File.ReadAllBytes((f))));
- var catalog = new AggregateCatalog(pluginAssemblies.Select(a => new AssemblyCatalog(a)));
+ var catalogs = new List<ComposablePartCatalog>();
+
+ catalogs.AddRange(pluginAssemblies.Select(a => new AssemblyCatalog(a)));
// Include composable parts in the Common assembly
- catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
+ catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
if (includeCurrentAssembly)
{
// Include composable parts in the subclass assembly
- catalog.Catalogs.Add(new AssemblyCatalog(GetType().Assembly));
+ catalogs.Add(new AssemblyCatalog(GetType().Assembly));
}
- return new CompositionContainer(catalog);
+ return MefUtils.GetSafeCompositionContainer(catalogs);
}
/// <summary>
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index ce5f4e34e..78d1fc98c 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -86,6 +86,7 @@
<Compile Include="Logging\BaseLogger.cs" />
<Compile Include="Logging\LogSeverity.cs" />
<Compile Include="Logging\TraceFileLogger.cs" />
+ <Compile Include="Mef\MefUtils.cs" />
<Compile Include="Net\Handlers\StaticFileHandler.cs" />
<Compile Include="Net\MimeTypes.cs" />
<Compile Include="Plugins\BaseTheme.cs" />
diff --git a/MediaBrowser.Common/Mef/MefUtils.cs b/MediaBrowser.Common/Mef/MefUtils.cs
new file mode 100644
index 000000000..55d888697
--- /dev/null
+++ b/MediaBrowser.Common/Mef/MefUtils.cs
@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using System.ComponentModel.Composition.Hosting;
+using System.ComponentModel.Composition.Primitives;
+using System.Linq;
+using System.Reflection;
+
+namespace MediaBrowser.Common.Mef
+{
+ public static class MefUtils
+ {
+ /// <summary>
+ /// Plugins that live on both the server and UI are going to have references to assemblies from both sides.
+ /// But looks for Parts on one side, it will throw an exception when it seems Types from the other side that it doesn't have a reference to.
+ /// For example, a plugin provides a Resolver. When MEF runs in the UI, it will throw an exception when it sees the resolver because there won't be a reference to the base class.
+ /// This method will catch those exceptions while retining the list of Types that MEF is able to resolve.
+ /// </summary>
+ public static CompositionContainer GetSafeCompositionContainer(IEnumerable<ComposablePartCatalog> catalogs)
+ {
+ var newList = new List<ComposablePartCatalog>();
+
+ // Go through each Catalog
+ foreach (var catalog in catalogs)
+ {
+ try
+ {
+ // Try to have MEF find Parts
+ catalog.Parts.ToArray();
+
+ // If it succeeds we can use the entire catalog
+ newList.Add(catalog);
+ }
+ catch (ReflectionTypeLoadException ex)
+ {
+ // If it fails we can still get a list of the Types it was able to resolve and create TypeCatalogs
+ var typeCatalogs = ex.Types.Where(t => t != null).Select(t => new TypeCatalog(t));
+ newList.AddRange(typeCatalogs);
+ }
+ }
+
+ return new CompositionContainer(new AggregateCatalog(newList));
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs
index a764c5eab..70e573817 100644
--- a/MediaBrowser.Common/Plugins/BasePlugin.cs
+++ b/MediaBrowser.Common/Plugins/BasePlugin.cs
@@ -4,6 +4,7 @@ using MediaBrowser.Common.Serialization;
using MediaBrowser.Model.Plugins;
using System;
using System.IO;
+using System.Reflection;
namespace MediaBrowser.Common.Plugins
{
@@ -12,7 +13,7 @@ namespace MediaBrowser.Common.Plugins
/// </summary>
public abstract class BasePlugin : IDisposable
{
- private IKernel Kernel { get; set; }
+ protected IKernel Kernel { get; private set; }
/// <summary>
/// Gets or sets the plugin's current context
diff --git a/MediaBrowser.Common/Plugins/BaseTheme.cs b/MediaBrowser.Common/Plugins/BaseTheme.cs
index f06825262..32a28258b 100644
--- a/MediaBrowser.Common/Plugins/BaseTheme.cs
+++ b/MediaBrowser.Common/Plugins/BaseTheme.cs
@@ -1,4 +1,12 @@
-
+using MediaBrowser.Common.Mef;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.ComponentModel.Composition.Hosting;
+using System.ComponentModel.Composition.Primitives;
+using System.Windows;
+using System.Windows.Controls;
+
namespace MediaBrowser.Common.Plugins
{
public abstract class BaseTheme : BasePlugin
@@ -10,5 +18,61 @@ namespace MediaBrowser.Common.Plugins
return true;
}
}
+
+ /// <summary>
+ /// Gets the MEF CompositionContainer
+ /// </summary>
+ private CompositionContainer CompositionContainer { get; set; }
+
+ /// <summary>
+ /// Gets the list of global resources
+ /// </summary>
+ [ImportMany(typeof(ResourceDictionary))]
+ public IEnumerable<ResourceDictionary> GlobalResources { get; private set; }
+
+ /// <summary>
+ /// Gets the list of pages
+ /// </summary>
+ [ImportMany(typeof(Page))]
+ public IEnumerable<Page> Pages { get; private set; }
+
+ /// <summary>
+ /// Gets the pack Uri of the Login page
+ /// </summary>
+ public abstract Uri LoginPageUri { get; }
+
+ protected override void InitializeInUi()
+ {
+ base.InitializeInUi();
+
+ ComposeParts();
+ }
+
+ private void ComposeParts()
+ {
+ var catalog = new AssemblyCatalog(GetType().Assembly);
+
+ CompositionContainer = MefUtils.GetSafeCompositionContainer(new ComposablePartCatalog[] { catalog });
+
+ CompositionContainer.ComposeParts(this);
+
+ CompositionContainer.Catalog.Dispose();
+ }
+
+ protected override void DisposeInUi()
+ {
+ base.DisposeInUi();
+
+ CompositionContainer.Dispose();
+ }
+
+ protected Uri GeneratePackUri(string relativePath)
+ {
+ string assemblyName = GetType().Assembly.GetName().Name;
+
+ string uri = string.Format("pack://application:,,,/{0};component/{1}", assemblyName, relativePath);
+
+ return new Uri(uri, UriKind.Absolute);
+ }
}
}
diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs
index bf03d1af1..73f87d341 100644
--- a/MediaBrowser.Controller/Kernel.cs
+++ b/MediaBrowser.Controller/Kernel.cs
@@ -1,6 +1,5 @@
using MediaBrowser.Common.Kernel;
using MediaBrowser.Common.Logging;
-using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO;
diff --git a/MediaBrowser.Plugins.DefaultTheme/Converters/TileBackgroundConverter.cs b/MediaBrowser.Plugins.DefaultTheme/Converters/TileBackgroundConverter.cs
new file mode 100644
index 000000000..a7a964693
--- /dev/null
+++ b/MediaBrowser.Plugins.DefaultTheme/Converters/TileBackgroundConverter.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace MediaBrowser.Plugins.DefaultTheme.Converters
+{
+ public class TileBackgroundConverter : IValueConverter
+ {
+ private static readonly Brush[] TileColors = new Brush[] {
+ new SolidColorBrush(Color.FromRgb((byte)111,(byte)189,(byte)69)),
+ new SolidColorBrush(Color.FromRgb((byte)75,(byte)179,(byte)221)),
+ new SolidColorBrush(Color.FromRgb((byte)65,(byte)100,(byte)165)),
+ new SolidColorBrush(Color.FromRgb((byte)225,(byte)32,(byte)38)),
+ new SolidColorBrush(Color.FromRgb((byte)128,(byte)0,(byte)128)),
+ new SolidColorBrush(Color.FromRgb((byte)0,(byte)128,(byte)64)),
+ new SolidColorBrush(Color.FromRgb((byte)0,(byte)148,(byte)255)),
+ new SolidColorBrush(Color.FromRgb((byte)255,(byte)0,(byte)199)),
+ new SolidColorBrush(Color.FromRgb((byte)255,(byte)135,(byte)15)),
+ new SolidColorBrush(Color.FromRgb((byte)127,(byte)0,(byte)55))
+
+ };
+
+ private static int _currentIndex = new Random(DateTime.Now.Millisecond).Next(0, TileColors.Length);
+
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ int index;
+
+ lock (TileColors)
+ {
+ index = (_currentIndex++) % TileColors.Length;
+ }
+
+ return TileColors[index++];
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/MediaBrowser.Plugins.DefaultTheme/Converters/WeatherImageConverter.cs b/MediaBrowser.Plugins.DefaultTheme/Converters/WeatherImageConverter.cs
new file mode 100644
index 000000000..0d73a1a6f
--- /dev/null
+++ b/MediaBrowser.Plugins.DefaultTheme/Converters/WeatherImageConverter.cs
@@ -0,0 +1,43 @@
+using MediaBrowser.Model.Weather;
+using System;
+using System.ComponentModel.Composition;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace MediaBrowser.Plugins.DefaultTheme.Converters
+{
+ [PartNotDiscoverable]
+ public class WeatherImageConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var weather = value as WeatherInfo;
+
+ if (weather != null)
+ {
+ switch (weather.CurrentWeather.Condition)
+ {
+ case WeatherConditions.Thunderstorm:
+ return "../Images/Weather/Thunder.png";
+ case WeatherConditions.Overcast:
+ return "../Images/Weather/Overcast.png";
+ case WeatherConditions.Mist:
+ case WeatherConditions.Sleet:
+ case WeatherConditions.Rain:
+ return "../Images/Weather/Rain.png";
+ case WeatherConditions.Blizzard:
+ case WeatherConditions.Snow:
+ return "../Images/Weather/Snow.png";
+ default:
+ return "../Images/Weather/Sunny.png";
+ }
+ }
+ return null;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/MediaBrowser.Plugins.DefaultTheme/MediaBrowser.Plugins.DefaultTheme.csproj b/MediaBrowser.Plugins.DefaultTheme/MediaBrowser.Plugins.DefaultTheme.csproj
index b15a31cc5..2fe9914ab 100644
--- a/MediaBrowser.Plugins.DefaultTheme/MediaBrowser.Plugins.DefaultTheme.csproj
+++ b/MediaBrowser.Plugins.DefaultTheme/MediaBrowser.Plugins.DefaultTheme.csproj
@@ -31,6 +31,9 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
+ <PropertyGroup>
+ <RunPostBuildEvent>Always</RunPostBuildEvent>
+ </PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
@@ -48,6 +51,12 @@
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="Pages\LoginPage.xaml.cs">
+ <DependentUpon>LoginPage.xaml</DependentUpon>
+ </Compile>
+ <Compile Include="Resources\AppResources.cs" />
+ <Compile Include="Converters\TileBackgroundConverter.cs" />
+ <Compile Include="Converters\WeatherImageConverter.cs" />
<Compile Include="Plugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
@@ -90,9 +99,28 @@
<Name>MediaBrowser.UI</Name>
</ProjectReference>
</ItemGroup>
+ <ItemGroup>
+ <Page Include="Pages\LoginPage.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:Compile</Generator>
+ </Page>
+ <Page Include="Resources\AppResources.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:Compile</Generator>
+ </Page>
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\CurrentUserDefault.png" />
+ <Resource Include="Resources\Images\UserLoginDefault.png" />
+ <Resource Include="Resources\Images\Weather\Overcast.png" />
+ <Resource Include="Resources\Images\Weather\Rain.png" />
+ <Resource Include="Resources\Images\Weather\Snow.png" />
+ <Resource Include="Resources\Images\Weather\Sunny.png" />
+ <Resource Include="Resources\Images\Weather\Thunder.png" />
+ </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
- <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\" /y</PostBuildEvent>
+ <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData-Server\Plugins\" /y</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
diff --git a/MediaBrowser.Plugins.DefaultTheme/Pages/LoginPage.xaml b/MediaBrowser.Plugins.DefaultTheme/Pages/LoginPage.xaml
new file mode 100644
index 000000000..4b1552bd8
--- /dev/null
+++ b/MediaBrowser.Plugins.DefaultTheme/Pages/LoginPage.xaml
@@ -0,0 +1,64 @@
+<base:BaseLoginPage x:Class="MediaBrowser.Plugins.DefaultTheme.Pages.LoginPage"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:base="clr-namespace:MediaBrowser.UI.Pages;assembly=MediaBrowser.UI"
+ xmlns:DTO="clr-namespace:MediaBrowser.Model.DTO;assembly=MediaBrowser.Model"
+ xmlns:controls="clr-namespace:MediaBrowser.UI.Controls;assembly=MediaBrowser.UI" mc:Ignorable="d"
+ d:DesignHeight="300"
+ d:DesignWidth="300"
+ Title="LoginPage">
+
+ <Page.Resources>
+ <ResourceDictionary>
+ <DataTemplate DataType="{x:Type DTO:DtoUser}">
+ <Grid HorizontalAlignment="Left" Margin="3">
+
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="auto"></ColumnDefinition>
+ <ColumnDefinition Width="475"></ColumnDefinition>
+ </Grid.ColumnDefinitions>
+
+ <controls:ExtendedImage HasImage="{Binding HasImage}"
+ PlaceHolderSource="../Resources/Images/UserLoginDefault.png"
+ Source="{Binding Converter={StaticResource UserImageConverter}, ConverterParameter='225,225,0,0'}"
+ Stretch="Uniform"
+ Width="225"
+ Height="225"
+ Background="{Binding Converter={StaticResource TileBackgroundConverter}}"/>
+ <TextBlock Text="{Binding Name}" VerticalAlignment="Top" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="0" Margin="25 30 0 0" FontSize="{StaticResource Heading2FontSize}"></TextBlock>
+ <TextBlock Text="{Binding Converter={StaticResource LastSeenTextConverter}}" VerticalAlignment="Center" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="0" Margin="25 80 0 0"></TextBlock>
+ </Grid>
+ </DataTemplate>
+
+ </ResourceDictionary>
+ </Page.Resources>
+ <Grid>
+
+ <Grid.RowDefinitions>
+ <RowDefinition Height="auto"></RowDefinition>
+ <RowDefinition Height="*"></RowDefinition>
+ </Grid.RowDefinitions>
+
+ <Image Style="{StaticResource MBLogoImageBlack}" Margin="0 0 0 10" Height="125" Stretch="Uniform" HorizontalAlignment="Left"></Image>
+
+ <Grid VerticalAlignment="Stretch" HorizontalAlignment="Center" Grid.Row="1">
+
+ <Grid.RowDefinitions>
+ <RowDefinition Height="auto"></RowDefinition>
+ <RowDefinition Height="*"></RowDefinition>
+ </Grid.RowDefinitions>
+
+ <TextBlock FontSize="{StaticResource Heading2FontSize}" Grid.Row="0" Margin="0 0 0 30">Select Profile</TextBlock>
+
+ <ListView HorizontalAlignment="Center" Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ItemsSource="{Binding Path=Users}" Style="{StaticResource ListViewStyle}" ItemContainerStyle="{StaticResource ListViewItemStyle}">
+ <ListView.ItemsPanel>
+ <ItemsPanelTemplate>
+ <WrapPanel Orientation="Vertical" />
+ </ItemsPanelTemplate>
+ </ListView.ItemsPanel>
+ </ListView>
+ </Grid>
+ </Grid>
+</base:BaseLoginPage>
diff --git a/MediaBrowser.Plugins.DefaultTheme/Pages/LoginPage.xaml.cs b/MediaBrowser.Plugins.DefaultTheme/Pages/LoginPage.xaml.cs
new file mode 100644
index 000000000..547443086
--- /dev/null
+++ b/MediaBrowser.Plugins.DefaultTheme/Pages/LoginPage.xaml.cs
@@ -0,0 +1,15 @@
+using MediaBrowser.UI.Pages;
+
+namespace MediaBrowser.Plugins.DefaultTheme.Pages
+{
+ /// <summary>
+ /// Interaction logic for LoginPage.xaml
+ /// </summary>
+ public partial class LoginPage : BaseLoginPage
+ {
+ public LoginPage()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/MediaBrowser.Plugins.DefaultTheme/Plugin.cs b/MediaBrowser.Plugins.DefaultTheme/Plugin.cs
index 9dd33f363..f12d53af1 100644
--- a/MediaBrowser.Plugins.DefaultTheme/Plugin.cs
+++ b/MediaBrowser.Plugins.DefaultTheme/Plugin.cs
@@ -1,4 +1,5 @@
using MediaBrowser.Common.Plugins;
+using System;
using System.ComponentModel.Composition;
namespace MediaBrowser.Plugins.DefaultTheme
@@ -11,9 +12,9 @@ namespace MediaBrowser.Plugins.DefaultTheme
get { return "Default Theme"; }
}
- protected override void InitializeInUi()
+ public override Uri LoginPageUri
{
- base.InitializeInUi();
+ get { return GeneratePackUri("Pages/LoginPage.xaml"); }
}
}
}
diff --git a/MediaBrowser.Plugins.DefaultTheme/Resources/AppResources.cs b/MediaBrowser.Plugins.DefaultTheme/Resources/AppResources.cs
new file mode 100644
index 000000000..28128c75b
--- /dev/null
+++ b/MediaBrowser.Plugins.DefaultTheme/Resources/AppResources.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel.Composition;
+using System.Windows;
+
+namespace MediaBrowser.Plugins.DefaultTheme.Resources
+{
+ [Export(typeof(ResourceDictionary))]
+ public partial class AppResources : ResourceDictionary
+ {
+ public AppResources()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/MediaBrowser.Plugins.DefaultTheme/Resources/AppResources.xaml b/MediaBrowser.Plugins.DefaultTheme/Resources/AppResources.xaml
new file mode 100644
index 000000000..c1e5adeb5
--- /dev/null
+++ b/MediaBrowser.Plugins.DefaultTheme/Resources/AppResources.xaml
@@ -0,0 +1,81 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:themeconverters="clr-namespace:MediaBrowser.Plugins.DefaultTheme.Converters"
+ x:Class="MediaBrowser.Plugins.DefaultTheme.Resources.AppResources">
+
+ <themeconverters:WeatherImageConverter x:Key="WeatherImageConverter"></themeconverters:WeatherImageConverter>
+ <themeconverters:TileBackgroundConverter x:Key="TileBackgroundConverter"></themeconverters:TileBackgroundConverter>
+
+ <Style x:Key="ListViewItemStyle" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource BaseListViewItemStyle}">
+
+ <Style.Resources>
+ <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="LightBlue"/>
+ </Style.Resources>
+
+ <Style.Triggers>
+ <Trigger Property="IsMouseOver" Value="True">
+ <Setter Property="Opacity" Value=".85" />
+ </Trigger>
+ </Style.Triggers>
+ </Style>
+
+ <!--Override MainWindow style-->
+ <Style TargetType="Window" x:Key="MainWindow" BasedOn="{StaticResource BaseWindow}">
+ <Setter Property="Background">
+ <Setter.Value>
+ <RadialGradientBrush RadiusX=".75" RadiusY=".75">
+ <GradientStop Color="White" Offset="0.0"/>
+ <GradientStop Color="WhiteSmoke" Offset="0.5"/>
+ <GradientStop Color="#cfcfcf" Offset="1.0"/>
+ </RadialGradientBrush>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <!--Override PageContentTemplate-->
+ <ControlTemplate x:Key="PageContentTemplate">
+
+ <Grid Margin="20 15 20 20">
+
+ <StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Right">
+
+ <!--Display CurrentUser-->
+ <StackPanel Orientation="Horizontal" Margin="0 0 30 0" Visibility="{Binding Path=CurrentUser,Converter={StaticResource CurrentUserVisibilityConverter}}">
+ <TextBlock FontSize="{StaticResource Heading2FontSize}" Text="{Binding Path=CurrentUser.Name}" Margin="0 0 5 0">
+ </TextBlock>
+ <Image>
+ <Image.Style>
+ <Style TargetType="{x:Type Image}">
+ <Setter Property="Image.Source" Value="Images\CurrentUserDefault.png" />
+ <Setter Property="Stretch" Value="None" />
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding Path=CurrentUser.HasImage}" Value="true">
+ <Setter Property="Image.Source" Value="{Binding Path=CurrentUser,Converter={StaticResource UserImageConverter}, ConverterParameter='0,64,0,0'}" />
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ </Image.Style>
+ </Image>
+ </StackPanel>
+
+ <!--Display Weather-->
+ <StackPanel Orientation="Horizontal" Margin="0 0 30 0" Visibility="{Binding Path=CurrentWeather,Converter={StaticResource WeatherVisibilityConverter}}">
+
+ <TextBlock FontSize="{StaticResource Heading2FontSize}" Text="{Binding Path=CurrentWeather,Converter={StaticResource WeatherTemperatureConverter}}" Margin="0 0 5 0">
+ </TextBlock>
+ <Image Stretch="None" Source="{Binding Path=CurrentWeather,Converter={StaticResource WeatherImageConverter}}"></Image>
+ </StackPanel>
+
+ <!--Display Clock-->
+ <TextBlock FontSize="{StaticResource Heading2FontSize}">
+ <TextBlock.Text>
+ <Binding Path="CurrentTime" Converter="{StaticResource DateTimeToStringConverter}" ConverterParameter="h:mm" />
+ </TextBlock.Text>
+ </TextBlock>
+ </StackPanel>
+
+ <Frame x:Name="PageFrame"></Frame>
+ </Grid>
+ </ControlTemplate>
+
+</ResourceDictionary> \ No newline at end of file
diff --git a/MediaBrowser.Plugins.DefaultTheme/Resources/Images/CurrentUserDefault.png b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/CurrentUserDefault.png
new file mode 100644
index 000000000..f272ed92a
--- /dev/null
+++ b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/CurrentUserDefault.png
Binary files differ
diff --git a/MediaBrowser.Plugins.DefaultTheme/Resources/Images/UserLoginDefault.png b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/UserLoginDefault.png
new file mode 100644
index 000000000..93a06e308
--- /dev/null
+++ b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/UserLoginDefault.png
Binary files differ
diff --git a/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Overcast.png b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Overcast.png
new file mode 100644
index 000000000..b9b6765c7
--- /dev/null
+++ b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Overcast.png
Binary files differ
diff --git a/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Rain.png b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Rain.png
new file mode 100644
index 000000000..2e526f895
--- /dev/null
+++ b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Rain.png
Binary files differ
diff --git a/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Snow.png b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Snow.png
new file mode 100644
index 000000000..94131ed2d
--- /dev/null
+++ b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Snow.png
Binary files differ
diff --git a/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Sunny.png b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Sunny.png
new file mode 100644
index 000000000..2a51cd544
--- /dev/null
+++ b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Sunny.png
Binary files differ
diff --git a/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Thunder.png b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Thunder.png
new file mode 100644
index 000000000..f413a2ed7
--- /dev/null
+++ b/MediaBrowser.Plugins.DefaultTheme/Resources/Images/Weather/Thunder.png
Binary files differ
diff --git a/MediaBrowser.ServerApplication/App.config b/MediaBrowser.ServerApplication/App.config
index 4b2ffa81d..a5c945338 100644
--- a/MediaBrowser.ServerApplication/App.config
+++ b/MediaBrowser.ServerApplication/App.config
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
- <add key="ProgramDataPath" value="..\..\..\ProgramData" />
+ <add key="ProgramDataPath" value="..\..\..\ProgramData-Server" />
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
diff --git a/MediaBrowser.Plugins.sln b/MediaBrowser.UI.sln
index cd237ebce..65130e3d9 100644
--- a/MediaBrowser.Plugins.sln
+++ b/MediaBrowser.UI.sln
@@ -1,11 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Plugins.DefaultTheme", "MediaBrowser.Plugins.DefaultTheme\MediaBrowser.Plugins.DefaultTheme.csproj", "{6E892999-711D-4E24-8BAC-DACF5BFA783A}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.UI", "..\MediaBrowserUI\MediaBrowser.UI\MediaBrowser.UI.csproj", "{B5ECE1FB-618E-420B-9A99-8E972D76920A}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.UI", "MediaBrowser.UI\MediaBrowser.UI.csproj", "{B5ECE1FB-618E-420B-9A99-8E972D76920A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}"
EndProject
@@ -13,36 +9,48 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model", "Media
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.ApiInteraction", "MediaBrowser.ApiInteraction\MediaBrowser.ApiInteraction.csproj", "{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Plugins.DefaultTheme", "MediaBrowser.Plugins.DefaultTheme\MediaBrowser.Plugins.DefaultTheme.csproj", "{6E892999-711D-4E24-8BAC-DACF5BFA783A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|Any CPU.Build.0 = Release|Any CPU
- {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU
{B5ECE1FB-618E-420B-9A99-8E972D76920A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5ECE1FB-618E-420B-9A99-8E972D76920A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B5ECE1FB-618E-420B-9A99-8E972D76920A}.Debug|x86.ActiveCfg = Debug|Any CPU
{B5ECE1FB-618E-420B-9A99-8E972D76920A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5ECE1FB-618E-420B-9A99-8E972D76920A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B5ECE1FB-618E-420B-9A99-8E972D76920A}.Release|x86.ActiveCfg = Release|Any CPU
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x86.ActiveCfg = Debug|Any CPU
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.ActiveCfg = Release|Any CPU
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.ActiveCfg = Debug|Any CPU
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.ActiveCfg = Release|Any CPU
{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Debug|x86.ActiveCfg = Debug|Any CPU
{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Release|Any CPU.ActiveCfg = Release|Any CPU
{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Release|Any CPU.Build.0 = Release|Any CPU
+ {921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Release|x86.ActiveCfg = Release|Any CPU
+ {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6E892999-711D-4E24-8BAC-DACF5BFA783A}.Release|x86.ActiveCfg = Release|Any CPU
+ {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/MediaBrowser.UI/App.config b/MediaBrowser.UI/App.config
new file mode 100644
index 000000000..018d3790f
--- /dev/null
+++ b/MediaBrowser.UI/App.config
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <appSettings>
+ <add key="ProgramDataPath" value="..\..\..\ProgramData-UI" />
+ </appSettings>
+ <startup>
+ <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
+ </startup>
+</configuration> \ No newline at end of file
diff --git a/MediaBrowser.UI/App.xaml b/MediaBrowser.UI/App.xaml
new file mode 100644
index 000000000..75318985c
--- /dev/null
+++ b/MediaBrowser.UI/App.xaml
@@ -0,0 +1,14 @@
+<z:BaseApplication x:Class="MediaBrowser.UI.App"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:z="clr-namespace:MediaBrowser.Common.UI;assembly=MediaBrowser.Common">
+ <Application.Resources>
+ <ResourceDictionary>
+ <ResourceDictionary.MergedDictionaries>
+ <ResourceDictionary Source="Resources/AppResources.xaml" />
+ <ResourceDictionary Source="Resources/MainWindowResources.xaml" />
+ <ResourceDictionary Source="Resources/NavBarResources.xaml"/>
+ </ResourceDictionary.MergedDictionaries>
+ </ResourceDictionary>
+ </Application.Resources>
+</z:BaseApplication> \ No newline at end of file
diff --git a/MediaBrowser.UI/App.xaml.cs b/MediaBrowser.UI/App.xaml.cs
new file mode 100644
index 000000000..6f2afa91c
--- /dev/null
+++ b/MediaBrowser.UI/App.xaml.cs
@@ -0,0 +1,213 @@
+using MediaBrowser.Common.Kernel;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Common.UI;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.DTO;
+using MediaBrowser.Model.Weather;
+using MediaBrowser.UI.Controller;
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media.Imaging;
+
+namespace MediaBrowser.UI
+{
+ /// <summary>
+ /// Interaction logic for App.xaml
+ /// </summary>
+ public partial class App : BaseApplication, IApplication
+ {
+ private Timer ClockTimer { get; set; }
+ private Timer ServerConfigurationTimer { get; set; }
+
+ public static App Instance
+ {
+ get
+ {
+ return Application.Current as App;
+ }
+ }
+
+ public DtoUser CurrentUser
+ {
+ get
+ {
+ return UIKernel.Instance.CurrentUser;
+ }
+ set
+ {
+ UIKernel.Instance.CurrentUser = value;
+ OnPropertyChanged("CurrentUser");
+ }
+ }
+
+ public ServerConfiguration ServerConfiguration
+ {
+ get
+ {
+ return UIKernel.Instance.ServerConfiguration;
+ }
+ set
+ {
+ UIKernel.Instance.ServerConfiguration = value;
+ OnPropertyChanged("ServerConfiguration");
+ }
+ }
+
+ private DateTime _currentTime = DateTime.Now;
+ public DateTime CurrentTime
+ {
+ get
+ {
+ return _currentTime;
+ }
+ private set
+ {
+ _currentTime = value;
+ OnPropertyChanged("CurrentTime");
+ }
+ }
+
+ private WeatherInfo _currentWeather;
+ public WeatherInfo CurrentWeather
+ {
+ get
+ {
+ return _currentWeather;
+ }
+ private set
+ {
+ _currentWeather = value;
+ OnPropertyChanged("CurrentWeather");
+ }
+ }
+
+ private BaseTheme _currentTheme;
+ public BaseTheme CurrentTheme
+ {
+ get
+ {
+ return _currentTheme;
+ }
+ private set
+ {
+ _currentTheme = value;
+ OnPropertyChanged("CurrentTheme");
+ }
+ }
+
+ [STAThread]
+ public static void Main()
+ {
+ RunApplication<App>("MediaBrowserUI");
+ }
+
+ #region BaseApplication Overrides
+ protected override IKernel InstantiateKernel()
+ {
+ return new UIKernel();
+ }
+
+ protected override Window InstantiateMainWindow()
+ {
+ return new MainWindow();
+ }
+
+ protected override void OnKernelLoaded()
+ {
+ base.OnKernelLoaded();
+
+ PropertyChanged += AppPropertyChanged;
+
+ // Update every 10 seconds
+ ClockTimer = new Timer(ClockTimerCallback, null, 0, 10000);
+
+ // Update every 30 minutes
+ ServerConfigurationTimer = new Timer(ServerConfigurationTimerCallback, null, 0, 1800000);
+
+ CurrentTheme = UIKernel.Instance.Plugins.OfType<BaseTheme>().First();
+
+ foreach (var resource in CurrentTheme.GlobalResources)
+ {
+ Resources.MergedDictionaries.Add(resource);
+ }
+ }
+ #endregion
+
+ async void AppPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName.Equals("ServerConfiguration"))
+ {
+ if (string.IsNullOrEmpty(ServerConfiguration.WeatherZipCode))
+ {
+ CurrentWeather = null;
+ }
+ else
+ {
+ CurrentWeather = await UIKernel.Instance.ApiClient.GetWeatherInfoAsync(ServerConfiguration.WeatherZipCode);
+ }
+ }
+ }
+
+ private void ClockTimerCallback(object stateInfo)
+ {
+ CurrentTime = DateTime.Now;
+ }
+
+ private async void ServerConfigurationTimerCallback(object stateInfo)
+ {
+ ServerConfiguration = await UIKernel.Instance.ApiClient.GetServerConfigurationAsync();
+ }
+
+ public async Task<Image> GetImage(string url)
+ {
+ var image = new Image();
+
+ image.Source = await GetBitmapImage(url);
+
+ return image;
+ }
+
+ public async Task<BitmapImage> GetBitmapImage(string url)
+ {
+ Stream stream = await UIKernel.Instance.ApiClient.GetImageStreamAsync(url);
+
+ BitmapImage bitmap = new BitmapImage();
+
+ bitmap.CacheOption = BitmapCacheOption.Default;
+
+ bitmap.BeginInit();
+ bitmap.StreamSource = stream;
+ bitmap.EndInit();
+
+ return bitmap;
+ }
+
+ public async Task LogoutUser()
+ {
+ CurrentUser = null;
+
+ if (ServerConfiguration.EnableUserProfiles)
+ {
+ Navigate(CurrentTheme.LoginPageUri);
+ }
+ else
+ {
+ DtoUser defaultUser = await UIKernel.Instance.ApiClient.GetDefaultUserAsync();
+ CurrentUser = defaultUser;
+
+ Navigate(new Uri("/Pages/HomePage.xaml", UriKind.Relative));
+ }
+ }
+
+ public void Navigate(Uri uri)
+ {
+ (MainWindow as MainWindow).Navigate(uri);
+ }
+ }
+}
diff --git a/MediaBrowser.UI/Configuration/UIApplicationConfiguration.cs b/MediaBrowser.UI/Configuration/UIApplicationConfiguration.cs
new file mode 100644
index 000000000..59c625178
--- /dev/null
+++ b/MediaBrowser.UI/Configuration/UIApplicationConfiguration.cs
@@ -0,0 +1,27 @@
+using MediaBrowser.Model.Configuration;
+
+namespace MediaBrowser.UI.Configuration
+{
+ /// <summary>
+ /// This is the UI's device configuration that applies regardless of which user is logged in.
+ /// </summary>
+ public class UIApplicationConfiguration : BaseApplicationConfiguration
+ {
+ /// <summary>
+ /// Gets or sets the server host name (myserver or 192.168.x.x)
+ /// </summary>
+ public string ServerHostName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the port number used by the API
+ /// </summary>
+ public int ServerApiPort { get; set; }
+
+ public UIApplicationConfiguration()
+ : base()
+ {
+ ServerHostName = "localhost";
+ ServerApiPort = 8096;
+ }
+ }
+}
diff --git a/MediaBrowser.UI/Configuration/UIApplicationPaths.cs b/MediaBrowser.UI/Configuration/UIApplicationPaths.cs
new file mode 100644
index 000000000..07cb54fc1
--- /dev/null
+++ b/MediaBrowser.UI/Configuration/UIApplicationPaths.cs
@@ -0,0 +1,8 @@
+using MediaBrowser.Common.Kernel;
+
+namespace MediaBrowser.UI.Configuration
+{
+ public class UIApplicationPaths : BaseApplicationPaths
+ {
+ }
+}
diff --git a/MediaBrowser.UI/Controller/PluginUpdater.cs b/MediaBrowser.UI/Controller/PluginUpdater.cs
new file mode 100644
index 000000000..d9fa48749
--- /dev/null
+++ b/MediaBrowser.UI/Controller/PluginUpdater.cs
@@ -0,0 +1,231 @@
+using MediaBrowser.Common.Logging;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Common.Serialization;
+using MediaBrowser.Model.DTO;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.ComponentModel.Composition.Hosting;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.UI.Controller
+{
+ /// <summary>
+ /// This keeps ui plugin assemblies in sync with plugins installed on the server
+ /// </summary>
+ public class PluginUpdater
+ {
+ /// <summary>
+ /// Gets the list of currently installed UI plugins
+ /// </summary>
+ [ImportMany(typeof(BasePlugin))]
+ private IEnumerable<BasePlugin> CurrentPlugins { get; set; }
+
+ private CompositionContainer CompositionContainer { get; set; }
+
+ public async Task<PluginUpdateResult> UpdatePlugins()
+ {
+ // First load the plugins that are currently installed
+ ReloadComposableParts();
+
+ Logger.LogInfo("Downloading list of installed plugins");
+ PluginInfo[] allInstalledPlugins = await UIKernel.Instance.ApiClient.GetInstalledPluginsAsync().ConfigureAwait(false);
+
+ IEnumerable<PluginInfo> uiPlugins = allInstalledPlugins.Where(p => p.DownloadToUI);
+
+ PluginUpdateResult result = new PluginUpdateResult();
+
+ result.DeletedPlugins = DeleteUninstalledPlugins(uiPlugins);
+
+ await DownloadPluginAssemblies(uiPlugins, result).ConfigureAwait(false);
+
+ // If any new assemblies were downloaded we'll have to reload the CurrentPlugins list
+ if (result.NewlyInstalledPlugins.Any())
+ {
+ ReloadComposableParts();
+ }
+
+ result.UpdatedConfigurations = await DownloadPluginConfigurations(uiPlugins).ConfigureAwait(false);
+
+ CompositionContainer.Dispose();
+
+ return result;
+ }
+
+ /// <summary>
+ /// Downloads plugin assemblies from the server, if they need to be installed or updated.
+ /// </summary>
+ private async Task DownloadPluginAssemblies(IEnumerable<PluginInfo> uiPlugins, PluginUpdateResult result)
+ {
+ List<PluginInfo> newlyInstalledPlugins = new List<PluginInfo>();
+ List<PluginInfo> updatedPlugins = new List<PluginInfo>();
+
+ // Loop through the list of plugins that are on the server
+ foreach (PluginInfo pluginInfo in uiPlugins)
+ {
+ // See if it is already installed in the UI
+ BasePlugin installedPlugin = CurrentPlugins.FirstOrDefault(p => p.AssemblyFileName.Equals(pluginInfo.AssemblyFileName, StringComparison.OrdinalIgnoreCase));
+
+ // Download the plugin if it is not present, or if the current version is out of date
+ bool downloadPlugin = installedPlugin == null;
+
+ if (installedPlugin != null)
+ {
+ Version serverVersion = Version.Parse(pluginInfo.Version);
+
+ downloadPlugin = serverVersion > installedPlugin.Version;
+ }
+
+ if (downloadPlugin)
+ {
+ await DownloadPlugin(pluginInfo).ConfigureAwait(false);
+
+ if (installedPlugin == null)
+ {
+ newlyInstalledPlugins.Add(pluginInfo);
+ }
+ else
+ {
+ updatedPlugins.Add(pluginInfo);
+ }
+ }
+ }
+
+ result.NewlyInstalledPlugins = newlyInstalledPlugins;
+ result.UpdatedPlugins = updatedPlugins;
+ }
+
+ /// <summary>
+ /// Downloads plugin configurations from the server.
+ /// </summary>
+ private async Task<List<PluginInfo>> DownloadPluginConfigurations(IEnumerable<PluginInfo> uiPlugins)
+ {
+ List<PluginInfo> updatedPlugins = new List<PluginInfo>();
+
+ // Loop through the list of plugins that are on the server
+ foreach (PluginInfo pluginInfo in uiPlugins)
+ {
+ // See if it is already installed in the UI
+ BasePlugin installedPlugin = CurrentPlugins.First(p => p.AssemblyFileName.Equals(pluginInfo.AssemblyFileName, StringComparison.OrdinalIgnoreCase));
+
+ if (installedPlugin.ConfigurationDateLastModified < pluginInfo.ConfigurationDateLastModified)
+ {
+ await DownloadPluginConfiguration(installedPlugin, pluginInfo).ConfigureAwait(false);
+
+ updatedPlugins.Add(pluginInfo);
+ }
+ }
+
+ return updatedPlugins;
+ }
+
+ /// <summary>
+ /// Downloads a plugin assembly from the server
+ /// </summary>
+ private async Task DownloadPlugin(PluginInfo plugin)
+ {
+ Logger.LogInfo("Downloading {0} Plugin", plugin.Name);
+
+ string path = Path.Combine(UIKernel.Instance.ApplicationPaths.PluginsPath, plugin.AssemblyFileName);
+
+ // First download to a MemoryStream. This way if the download is cut off, we won't be left with a partial file
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ Stream assemblyStream = await UIKernel.Instance.ApiClient.GetPluginAssemblyAsync(plugin).ConfigureAwait(false);
+
+ await assemblyStream.CopyToAsync(memoryStream).ConfigureAwait(false);
+
+ memoryStream.Position = 0;
+
+ using (FileStream fileStream = new FileStream(path, FileMode.Create))
+ {
+ await memoryStream.CopyToAsync(fileStream).ConfigureAwait(false);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Downloads the latest configuration for a plugin
+ /// </summary>
+ private async Task DownloadPluginConfiguration(BasePlugin plugin, PluginInfo pluginInfo)
+ {
+ Logger.LogInfo("Downloading {0} Configuration", plugin.Name);
+
+ object config = await UIKernel.Instance.ApiClient.GetPluginConfigurationAsync(pluginInfo, plugin.ConfigurationType).ConfigureAwait(false);
+
+ XmlSerializer.SerializeToFile(config, plugin.ConfigurationFilePath);
+
+ File.SetLastWriteTimeUtc(plugin.ConfigurationFilePath, pluginInfo.ConfigurationDateLastModified);
+ }
+
+ /// <summary>
+ /// Deletes any plugins that have been uninstalled from the server
+ /// </summary>
+ private IEnumerable<string> DeleteUninstalledPlugins(IEnumerable<PluginInfo> uiPlugins)
+ {
+ var deletedPlugins = new List<string>();
+
+ foreach (BasePlugin plugin in CurrentPlugins)
+ {
+ PluginInfo latest = uiPlugins.FirstOrDefault(p => p.AssemblyFileName.Equals(plugin.AssemblyFileName, StringComparison.OrdinalIgnoreCase));
+
+ if (latest == null)
+ {
+ DeletePlugin(plugin);
+
+ deletedPlugins.Add(plugin.Name);
+ }
+ }
+
+ return deletedPlugins;
+ }
+
+ /// <summary>
+ /// Deletes an installed ui plugin.
+ /// Leaves config and data behind in the event it is later re-installed
+ /// </summary>
+ private void DeletePlugin(BasePlugin plugin)
+ {
+ Logger.LogInfo("Deleting {0} Plugin", plugin.Name);
+
+ string path = plugin.AssemblyFilePath;
+
+ if (File.Exists(path))
+ {
+ File.Delete(path);
+ }
+ }
+
+ /// <summary>
+ /// Re-uses MEF within the kernel to discover installed plugins
+ /// </summary>
+ private void ReloadComposableParts()
+ {
+ if (CompositionContainer != null)
+ {
+ CompositionContainer.Dispose();
+ }
+
+ CompositionContainer = UIKernel.Instance.GetCompositionContainer();
+
+ CompositionContainer.ComposeParts(this);
+
+ CompositionContainer.Catalog.Dispose();
+
+ foreach (BasePlugin plugin in CurrentPlugins)
+ {
+ plugin.Initialize(UIKernel.Instance, false);
+ }
+ }
+ }
+
+ public class PluginUpdateResult
+ {
+ public IEnumerable<string> DeletedPlugins { get; set; }
+ public IEnumerable<PluginInfo> NewlyInstalledPlugins { get; set; }
+ public IEnumerable<PluginInfo> UpdatedPlugins { get; set; }
+ public IEnumerable<PluginInfo> UpdatedConfigurations { get; set; }
+ }
+}
diff --git a/MediaBrowser.UI/Controller/UIKernel.cs b/MediaBrowser.UI/Controller/UIKernel.cs
new file mode 100644
index 000000000..ca24b7852
--- /dev/null
+++ b/MediaBrowser.UI/Controller/UIKernel.cs
@@ -0,0 +1,97 @@
+using MediaBrowser.ApiInteraction;
+using MediaBrowser.Common.Kernel;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.DTO;
+using MediaBrowser.Model.Progress;
+using MediaBrowser.UI.Configuration;
+using System;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.UI.Controller
+{
+ /// <summary>
+ /// This controls application logic as well as server interaction within the UI.
+ /// </summary>
+ public class UIKernel : BaseKernel<UIApplicationConfiguration, UIApplicationPaths>
+ {
+ public static UIKernel Instance { get; private set; }
+
+ public ApiClient ApiClient { get; private set; }
+ public DtoUser CurrentUser { get; set; }
+ public ServerConfiguration ServerConfiguration { get; set; }
+
+ public UIKernel()
+ : base()
+ {
+ Instance = this;
+ }
+
+ public override KernelContext KernelContext
+ {
+ get { return KernelContext.Ui; }
+ }
+
+ /// <summary>
+ /// Give the UI a different url prefix so that they can share the same port, in case they are installed on the same machine.
+ /// </summary>
+ protected override string HttpServerUrlPrefix
+ {
+ get
+ {
+ return "http://+:" + Configuration.HttpServerPortNumber + "/mediabrowser/ui/";
+ }
+ }
+
+ /// <summary>
+ /// Performs initializations that can be reloaded at anytime
+ /// </summary>
+ protected override async Task ReloadInternal(IProgress<TaskProgress> progress)
+ {
+ ReloadApiClient();
+
+ await new PluginUpdater().UpdatePlugins().ConfigureAwait(false);
+
+ await base.ReloadInternal(progress).ConfigureAwait(false);
+ }
+
+ /// <summary>
+ /// Updates and installs new plugin assemblies and configurations from the server
+ /// </summary>
+ protected async Task<PluginUpdateResult> UpdatePlugins()
+ {
+ return await new PluginUpdater().UpdatePlugins().ConfigureAwait(false);
+ }
+
+ /// <summary>
+ /// Disposes the current ApiClient and creates a new one
+ /// </summary>
+ private void ReloadApiClient()
+ {
+ DisposeApiClient();
+
+ ApiClient = new ApiClient
+ {
+ ServerHostName = Configuration.ServerHostName,
+ ServerApiPort = Configuration.ServerApiPort
+ };
+ }
+
+ /// <summary>
+ /// Disposes the current ApiClient
+ /// </summary>
+ private void DisposeApiClient()
+ {
+ if (ApiClient != null)
+ {
+ ApiClient.Dispose();
+ }
+ }
+
+ public override void Dispose()
+ {
+ base.Dispose();
+
+ DisposeApiClient();
+ }
+ }
+}
diff --git a/MediaBrowser.UI/Controls/EnhancedScrollViewer.cs b/MediaBrowser.UI/Controls/EnhancedScrollViewer.cs
new file mode 100644
index 000000000..188715e1e
--- /dev/null
+++ b/MediaBrowser.UI/Controls/EnhancedScrollViewer.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Provides a ScrollViewer that can be scrolled by dragging the mouse
+ /// </summary>
+ public class EnhancedScrollViewer : ScrollViewer
+ {
+ private Point _scrollTarget;
+ private Point _scrollStartPoint;
+ private Point _scrollStartOffset;
+ private const int PixelsToMoveToBeConsideredScroll = 5;
+
+ protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
+ {
+ if (IsMouseOver)
+ {
+ // Save starting point, used later when determining how much to scroll.
+ _scrollStartPoint = e.GetPosition(this);
+ _scrollStartOffset.X = HorizontalOffset;
+ _scrollStartOffset.Y = VerticalOffset;
+
+ // Update the cursor if can scroll or not.
+ Cursor = (ExtentWidth > ViewportWidth) ||
+ (ExtentHeight > ViewportHeight) ?
+ Cursors.ScrollAll : Cursors.Arrow;
+
+ CaptureMouse();
+ }
+
+ base.OnPreviewMouseDown(e);
+ }
+
+ protected override void OnPreviewMouseMove(MouseEventArgs e)
+ {
+ if (IsMouseCaptured)
+ {
+ Point currentPoint = e.GetPosition(this);
+
+ // Determine the new amount to scroll.
+ var delta = new Point(_scrollStartPoint.X - currentPoint.X, _scrollStartPoint.Y - currentPoint.Y);
+
+ if (Math.Abs(delta.X) < PixelsToMoveToBeConsideredScroll &&
+ Math.Abs(delta.Y) < PixelsToMoveToBeConsideredScroll)
+ return;
+
+ _scrollTarget.X = _scrollStartOffset.X + delta.X;
+ _scrollTarget.Y = _scrollStartOffset.Y + delta.Y;
+
+ // Scroll to the new position.
+ ScrollToHorizontalOffset(_scrollTarget.X);
+ ScrollToVerticalOffset(_scrollTarget.Y);
+ }
+
+ base.OnPreviewMouseMove(e);
+ }
+
+ protected override void OnPreviewMouseUp(MouseButtonEventArgs e)
+ {
+ if (IsMouseCaptured)
+ {
+ Cursor = Cursors.Arrow;
+ ReleaseMouseCapture();
+ }
+
+ base.OnPreviewMouseUp(e);
+ }
+ }
+}
diff --git a/MediaBrowser.UI/Controls/ExtendedImage.cs b/MediaBrowser.UI/Controls/ExtendedImage.cs
new file mode 100644
index 000000000..9d6ee3a7a
--- /dev/null
+++ b/MediaBrowser.UI/Controls/ExtendedImage.cs
@@ -0,0 +1,92 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
+ ///
+ /// Step 1a) Using this custom control in a XAML file that exists in the current project.
+ /// Add this XmlNamespace attribute to the root element of the markup file where it is
+ /// to be used:
+ ///
+ /// xmlns:MyNamespace="clr-namespace:MediaBrowser.UI.Controls"
+ ///
+ ///
+ /// Step 1b) Using this custom control in a XAML file that exists in a different project.
+ /// Add this XmlNamespace attribute to the root element of the markup file where it is
+ /// to be used:
+ ///
+ /// xmlns:MyNamespace="clr-namespace:MediaBrowser.UI.Controls;assembly=MediaBrowser.UI.Controls"
+ ///
+ /// You will also need to add a project reference from the project where the XAML file lives
+ /// to this project and Rebuild to avoid compilation errors:
+ ///
+ /// Right click on the target project in the Solution Explorer and
+ /// "Add Reference"->"Projects"->[Browse to and select this project]
+ ///
+ ///
+ /// Step 2)
+ /// Go ahead and use your control in the XAML file.
+ ///
+ /// <MyNamespace:ExtendedImage/>
+ ///
+ /// </summary>
+ public class ExtendedImage : Control
+ {
+ public static readonly DependencyProperty HasImageProperty = DependencyProperty.Register(
+ "HasImage",
+ typeof (bool),
+ typeof (ExtendedImage),
+ new PropertyMetadata(default(bool)));
+
+ public bool HasImage
+ {
+ get { return (bool)GetValue(HasImageProperty); }
+ set { SetValue(HasImageProperty, value); }
+ }
+
+ public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
+ "Source",
+ typeof(ImageSource),
+ typeof(ExtendedImage),
+ new PropertyMetadata(default(ImageBrush)));
+
+ public ImageSource Source
+ {
+ get { return (ImageSource)GetValue(SourceProperty); }
+ set { SetValue(SourceProperty, value); }
+ }
+
+ public static readonly DependencyProperty StretchProperty = DependencyProperty.Register(
+ "Stretch",
+ typeof (Stretch),
+ typeof (ExtendedImage),
+ new PropertyMetadata(default(Stretch)));
+
+ public Stretch Stretch
+ {
+ get { return (Stretch) GetValue(StretchProperty); }
+ set { SetValue(StretchProperty, value); }
+ }
+
+ public static readonly DependencyProperty PlaceHolderSourceProperty = DependencyProperty.Register(
+ "PlaceHolderSource",
+ typeof(ImageSource),
+ typeof(ExtendedImage),
+ new PropertyMetadata(default(ImageBrush)));
+
+ public ImageSource PlaceHolderSource
+ {
+ get { return (ImageSource)GetValue(PlaceHolderSourceProperty); }
+ set { SetValue(PlaceHolderSourceProperty, value); }
+ }
+
+ static ExtendedImage()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(typeof(ExtendedImage),
+ new FrameworkPropertyMetadata(typeof(ExtendedImage)));
+ }
+ }
+}
diff --git a/MediaBrowser.UI/Controls/TreeHelper.cs b/MediaBrowser.UI/Controls/TreeHelper.cs
new file mode 100644
index 000000000..bbe489572
--- /dev/null
+++ b/MediaBrowser.UI/Controls/TreeHelper.cs
@@ -0,0 +1,226 @@
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Media;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Helper methods for UI-related tasks.
+ /// </summary>
+ public static class TreeHelper
+ {
+ /// <summary>
+ /// Finds a Child of a given item in the visual tree.
+ /// </summary>
+ /// <param name="parent">A direct parent of the queried item.</param>
+ /// <typeparam name="T">The type of the queried item.</typeparam>
+ /// <param name="childName">x:Name or Name of child. </param>
+ /// <returns>The first parent item that matches the submitted type parameter.
+ /// If not matching item can be found,
+ /// a null parent is being returned.</returns>
+ public static T FindChild<T>(DependencyObject parent, string childName)
+ where T : DependencyObject
+ {
+ // Confirm parent and childName are valid.
+ if (parent == null) return null;
+
+ T foundChild = null;
+
+ int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
+ for (int i = 0; i < childrenCount; i++)
+ {
+ var child = VisualTreeHelper.GetChild(parent, i);
+ // If the child is not of the request child type child
+ T childType = child as T;
+ if (childType == null)
+ {
+ // recursively drill down the tree
+ foundChild = FindChild<T>(child, childName);
+
+ // If the child is found, break so we do not overwrite the found child.
+ if (foundChild != null) break;
+ }
+ else if (!string.IsNullOrEmpty(childName))
+ {
+ var frameworkElement = child as FrameworkElement;
+ // If the child's name is set for search
+ if (frameworkElement != null && frameworkElement.Name == childName)
+ {
+ // if the child's name is of the request name
+ foundChild = (T)child;
+ break;
+ }
+ }
+ else
+ {
+ // child element found.
+ foundChild = (T)child;
+ break;
+ }
+ }
+
+ return foundChild;
+ }
+
+ #region find parent
+
+ /// <summary>
+ /// Finds a parent of a given item on the visual tree.
+ /// </summary>
+ /// <typeparam name="T">The type of the queried item.</typeparam>
+ /// <param name="child">A direct or indirect child of the
+ /// queried item.</param>
+ /// <returns>The first parent item that matches the submitted
+ /// type parameter. If not matching item can be found, a null
+ /// reference is being returned.</returns>
+ public static T TryFindParent<T>(this DependencyObject child)
+ where T : DependencyObject
+ {
+ //get parent item
+ DependencyObject parentObject = GetParentObject(child);
+
+ //we've reached the end of the tree
+ if (parentObject == null) return null;
+
+ //check if the parent matches the type we're looking for
+ T parent = parentObject as T;
+ if (parent != null)
+ {
+ return parent;
+ }
+
+ //use recursion to proceed with next level
+ return TryFindParent<T>(parentObject);
+ }
+
+ /// <summary>
+ /// This method is an alternative to WPF's
+ /// <see cref="VisualTreeHelper.GetParent"/> method, which also
+ /// supports content elements. Keep in mind that for content element,
+ /// this method falls back to the logical tree of the element!
+ /// </summary>
+ /// <param name="child">The item to be processed.</param>
+ /// <returns>The submitted item's parent, if available. Otherwise
+ /// null.</returns>
+ public static DependencyObject GetParentObject(this DependencyObject child)
+ {
+ if (child == null) return null;
+
+ //handle content elements separately
+ ContentElement contentElement = child as ContentElement;
+ if (contentElement != null)
+ {
+ DependencyObject parent = ContentOperations.GetParent(contentElement);
+ if (parent != null) return parent;
+
+ FrameworkContentElement fce = contentElement as FrameworkContentElement;
+ return fce != null ? fce.Parent : null;
+ }
+
+ //also try searching for parent in framework elements (such as DockPanel, etc)
+ FrameworkElement frameworkElement = child as FrameworkElement;
+ if (frameworkElement != null)
+ {
+ DependencyObject parent = frameworkElement.Parent;
+ if (parent != null) return parent;
+ }
+
+ //if it's not a ContentElement/FrameworkElement, rely on VisualTreeHelper
+ return VisualTreeHelper.GetParent(child);
+ }
+
+ #endregion
+
+ #region find children
+
+ /// <summary>
+ /// Analyzes both visual and logical tree in order to find all elements of a given
+ /// type that are descendants of the <paramref name="source"/> item.
+ /// </summary>
+ /// <typeparam name="T">The type of the queried items.</typeparam>
+ /// <param name="source">The root element that marks the source of the search. If the
+ /// source is already of the requested type, it will not be included in the result.</param>
+ /// <returns>All descendants of <paramref name="source"/> that match the requested type.</returns>
+ public static IEnumerable<T> FindChildren<T>(this DependencyObject source) where T : DependencyObject
+ {
+ if (source != null)
+ {
+ var childs = GetChildObjects(source);
+ foreach (DependencyObject child in childs)
+ {
+ //analyze if children match the requested type
+ if (child is T)
+ {
+ yield return (T)child;
+ }
+
+ //recurse tree
+ foreach (T descendant in FindChildren<T>(child))
+ {
+ yield return descendant;
+ }
+ }
+ }
+ }
+
+
+ /// <summary>
+ /// This method is an alternative to WPF's
+ /// <see cref="VisualTreeHelper.GetChild"/> method, which also
+ /// supports content elements. Keep in mind that for content elements,
+ /// this method falls back to the logical tree of the element.
+ /// </summary>
+ /// <param name="parent">The item to be processed.</param>
+ /// <returns>The submitted item's child elements, if available.</returns>
+ public static IEnumerable<DependencyObject> GetChildObjects(this DependencyObject parent)
+ {
+ if (parent == null) yield break;
+
+ if (parent is ContentElement || parent is FrameworkElement)
+ {
+ //use the logical tree for content / framework elements
+ foreach (object obj in LogicalTreeHelper.GetChildren(parent))
+ {
+ var depObj = obj as DependencyObject;
+ if (depObj != null) yield return (DependencyObject)obj;
+ }
+ }
+ else
+ {
+ //use the visual tree per default
+ int count = VisualTreeHelper.GetChildrenCount(parent);
+ for (int i = 0; i < count; i++)
+ {
+ yield return VisualTreeHelper.GetChild(parent, i);
+ }
+ }
+ }
+
+ #endregion
+
+ #region find from point
+
+ /// <summary>
+ /// Tries to locate a given item within the visual tree,
+ /// starting with the dependency object at a given position.
+ /// </summary>
+ /// <typeparam name="T">The type of the element to be found
+ /// on the visual tree of the element at the given location.</typeparam>
+ /// <param name="reference">The main element which is used to perform
+ /// hit testing.</param>
+ /// <param name="point">The position to be evaluated on the origin.</param>
+ public static T TryFindFromPoint<T>(UIElement reference, Point point)
+ where T : DependencyObject
+ {
+ DependencyObject element = reference.InputHitTest(point) as DependencyObject;
+
+ if (element == null) return null;
+
+ if (element is T) return (T)element;
+
+ return TryFindParent<T>(element);
+ }
+
+ #endregion
+ }
+}
diff --git a/MediaBrowser.UI/Controls/WindowCommands.xaml b/MediaBrowser.UI/Controls/WindowCommands.xaml
new file mode 100644
index 000000000..920954918
--- /dev/null
+++ b/MediaBrowser.UI/Controls/WindowCommands.xaml
@@ -0,0 +1,91 @@
+<UserControl x:Class="MediaBrowser.UI.Controls.WindowCommands"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ mc:Ignorable="d"
+ d:DesignHeight="300" d:DesignWidth="300">
+
+ <UserControl.Resources>
+
+ <Style TargetType="StackPanel" x:Key="WindowCommandsPanel">
+ <Setter Property="Orientation" Value="Horizontal"/>
+ <Setter Property="HorizontalAlignment" Value="Right"/>
+ </Style>
+
+ <Style TargetType="Button" x:Key="WebdingsButton" BasedOn="{StaticResource ImageButton}">
+ <Setter Property="Margin" Value="0 0 15 0"/>
+ <Setter Property="KeyboardNavigation.IsTabStop" Value="false"/>
+ </Style>
+
+ <Style TargetType="TextBlock" x:Key="WebdingsTextBlock">
+ <Setter Property="FontFamily" Value="Webdings"/>
+ <Setter Property="FontSize" Value="14"/>
+ <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+ </Style>
+
+ <Style TargetType="Button" x:Key="MinimizeApplicationButton" BasedOn="{StaticResource WebdingsButton}">
+ <Setter Property="ToolTip" Value="Minimize"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <TextBlock Style="{StaticResource WebdingsTextBlock}">0</TextBlock>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Button" x:Key="MaximizeApplicationButton" BasedOn="{StaticResource WebdingsButton}">
+ <Setter Property="ToolTip" Value="Maximize"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <TextBlock Style="{StaticResource WebdingsTextBlock}">1</TextBlock>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding Path=WindowState, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="Maximized">
+ <Setter Property="Visibility" Value="Collapsed" />
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+
+ <Style TargetType="Button" x:Key="UndoMaximizeApplicationButton" BasedOn="{StaticResource WebdingsButton}">
+ <Setter Property="Visibility" Value="Collapsed"/>
+ <Setter Property="ToolTip" Value="Restore"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <TextBlock Style="{StaticResource WebdingsTextBlock}">2</TextBlock>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding Path=WindowState, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="Maximized">
+ <Setter Property="Visibility" Value="Visible" />
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+
+ <Style TargetType="Button" x:Key="CloseApplicationButton" BasedOn="{StaticResource WebdingsButton}">
+ <Setter Property="ToolTip" Value="Close"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <TextBlock Style="{StaticResource WebdingsTextBlock}">r</TextBlock>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ </UserControl.Resources>
+
+ <StackPanel Style="{StaticResource WindowCommandsPanel}">
+ <Button x:Name="MinimizeApplicationButton" Style="{StaticResource MinimizeApplicationButton}"></Button>
+ <Button x:Name="MaximizeApplicationButton" Style="{StaticResource MaximizeApplicationButton}"></Button>
+ <Button x:Name="UndoMaximizeApplicationButton" Style="{StaticResource UndoMaximizeApplicationButton}"></Button>
+ <Button x:Name="CloseApplicationButton" Style="{StaticResource CloseApplicationButton}"></Button>
+ </StackPanel>
+
+</UserControl>
diff --git a/MediaBrowser.UI/Controls/WindowCommands.xaml.cs b/MediaBrowser.UI/Controls/WindowCommands.xaml.cs
new file mode 100644
index 000000000..1810c5bf3
--- /dev/null
+++ b/MediaBrowser.UI/Controls/WindowCommands.xaml.cs
@@ -0,0 +1,50 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace MediaBrowser.UI.Controls
+{
+ /// <summary>
+ /// Interaction logic for WindowCommands.xaml
+ /// </summary>
+ public partial class WindowCommands : UserControl
+ {
+ public Window ParentWindow
+ {
+ get { return TreeHelper.TryFindParent<Window>(this); }
+ }
+
+ public WindowCommands()
+ {
+ InitializeComponent();
+ Loaded += WindowCommandsLoaded;
+ }
+
+ void WindowCommandsLoaded(object sender, RoutedEventArgs e)
+ {
+ CloseApplicationButton.Click += CloseApplicationButtonClick;
+ MinimizeApplicationButton.Click += MinimizeApplicationButtonClick;
+ MaximizeApplicationButton.Click += MaximizeApplicationButtonClick;
+ UndoMaximizeApplicationButton.Click += UndoMaximizeApplicationButtonClick;
+ }
+
+ void UndoMaximizeApplicationButtonClick(object sender, RoutedEventArgs e)
+ {
+ ParentWindow.WindowState = WindowState.Normal;
+ }
+
+ void MaximizeApplicationButtonClick(object sender, RoutedEventArgs e)
+ {
+ ParentWindow.WindowState = WindowState.Maximized;
+ }
+
+ void MinimizeApplicationButtonClick(object sender, RoutedEventArgs e)
+ {
+ ParentWindow.WindowState = WindowState.Minimized;
+ }
+
+ void CloseApplicationButtonClick(object sender, RoutedEventArgs e)
+ {
+ ParentWindow.Close();
+ }
+ }
+}
diff --git a/MediaBrowser.UI/Converters/CurrentUserVisibilityConverter.cs b/MediaBrowser.UI/Converters/CurrentUserVisibilityConverter.cs
new file mode 100644
index 000000000..a5dd5013b
--- /dev/null
+++ b/MediaBrowser.UI/Converters/CurrentUserVisibilityConverter.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace MediaBrowser.UI.Converters
+{
+ public class CurrentUserVisibilityConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (App.Instance.ServerConfiguration == null || !App.Instance.ServerConfiguration.EnableUserProfiles)
+ {
+ return Visibility.Collapsed;
+ }
+
+ return value == null ? Visibility.Collapsed : Visibility.Visible;
+ }
+
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/MediaBrowser.UI/Converters/DateTimeToStringConverter.cs b/MediaBrowser.UI/Converters/DateTimeToStringConverter.cs
new file mode 100644
index 000000000..6c568c061
--- /dev/null
+++ b/MediaBrowser.UI/Converters/DateTimeToStringConverter.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace MediaBrowser.UI.Converters
+{
+ public class DateTimeToStringConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var date = (DateTime)value;
+
+ string format = parameter as string;
+
+ if (string.IsNullOrEmpty(format))
+ {
+ return date.ToString();
+ }
+
+ if (format.Equals("shorttime", StringComparison.OrdinalIgnoreCase))
+ {
+ return date.ToShortTimeString();
+ }
+
+ return date.ToString(format);
+ }
+
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/MediaBrowser.UI/Converters/LastSeenTextConverter.cs b/MediaBrowser.UI/Converters/LastSeenTextConverter.cs
new file mode 100644
index 000000000..746260210
--- /dev/null
+++ b/MediaBrowser.UI/Converters/LastSeenTextConverter.cs
@@ -0,0 +1,86 @@
+using MediaBrowser.Model.DTO;
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace MediaBrowser.UI.Converters
+{
+ public class LastSeenTextConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var user = value as DtoUser;
+
+ if (user != null)
+ {
+ if (user.LastActivityDate.HasValue)
+ {
+ DateTime date = user.LastActivityDate.Value.ToLocalTime();
+
+ return "Last seen " + GetRelativeTimeText(date);
+ }
+ }
+
+ return null;
+ }
+
+ private static string GetRelativeTimeText(DateTime date)
+ {
+ TimeSpan ts = DateTime.Now - date;
+
+ const int second = 1;
+ const int minute = 60 * second;
+ const int hour = 60 * minute;
+ const int day = 24 * hour;
+ const int month = 30 * day;
+
+ int delta = System.Convert.ToInt32(ts.TotalSeconds);
+
+ if (delta < 0)
+ {
+ return "not yet";
+ }
+ if (delta < 1 * minute)
+ {
+ return ts.Seconds == 1 ? "one second ago" : ts.Seconds + " seconds ago";
+ }
+ if (delta < 2 * minute)
+ {
+ return "a minute ago";
+ }
+ if (delta < 45 * minute)
+ {
+ return ts.Minutes + " minutes ago";
+ }
+ if (delta < 90 * minute)
+ {
+ return "an hour ago";
+ }
+ if (delta < 24 * hour)
+ {
+ return ts.Hours + " hours ago";
+ }
+ if (delta < 48 * hour)
+ {
+ return "yesterday";
+ }
+ if (delta < 30 * day)
+ {
+ return ts.Days + " days ago";
+ }
+ if (delta < 12 * month)
+ {
+ int months = System.Convert.ToInt32(Math.Floor((double)ts.Days / 30));
+ return months <= 1 ? "one month ago" : months + " months ago";
+ }
+
+ int years = System.Convert.ToInt32(Math.Floor((double)ts.Days / 365));
+ return years <= 1 ? "one year ago" : years + " years ago";
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/MediaBrowser.UI/Converters/UserImageConverter.cs b/MediaBrowser.UI/Converters/UserImageConverter.cs
new file mode 100644
index 000000000..a9ef4b862
--- /dev/null
+++ b/MediaBrowser.UI/Converters/UserImageConverter.cs
@@ -0,0 +1,60 @@
+using MediaBrowser.Model.DTO;
+using MediaBrowser.UI.Controller;
+using System;
+using System.Globalization;
+using System.Net.Cache;
+using System.Windows.Data;
+using System.Windows.Media.Imaging;
+
+namespace MediaBrowser.UI.Converters
+{
+ public class UserImageConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var user = value as DtoUser;
+
+ if (user != null && user.HasImage)
+ {
+ var config = parameter as string;
+
+ int? maxWidth = null;
+ int? maxHeight = null;
+ int? width = null;
+ int? height = null;
+
+ if (!string.IsNullOrEmpty(config))
+ {
+ var vals = config.Split(',');
+
+ width = GetSize(vals[0]);
+ height = GetSize(vals[1]);
+ maxWidth = GetSize(vals[2]);
+ maxHeight = GetSize(vals[3]);
+ }
+
+ var uri = UIKernel.Instance.ApiClient.GetUserImageUrl(user.Id, width, height, maxWidth, maxHeight, 100);
+
+ return new BitmapImage(new Uri(uri), new RequestCachePolicy(RequestCacheLevel.Revalidate));
+ }
+
+ return null;
+ }
+
+ private int? GetSize(string val)
+ {
+ if (string.IsNullOrEmpty(val) || val == "0")
+ {
+ return null;
+ }
+
+ return int.Parse(val);
+ }
+
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/MediaBrowser.UI/Converters/WeatherTemperatureConverter.cs b/MediaBrowser.UI/Converters/WeatherTemperatureConverter.cs
new file mode 100644
index 000000000..cab4c595c
--- /dev/null
+++ b/MediaBrowser.UI/Converters/WeatherTemperatureConverter.cs
@@ -0,0 +1,31 @@
+using MediaBrowser.Model.Weather;
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace MediaBrowser.UI.Converters
+{
+ public class WeatherTemperatureConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var weather = value as WeatherInfo;
+
+ if (weather != null)
+ {
+ if (App.Instance.ServerConfiguration.WeatherUnit == WeatherUnits.Celsius)
+ {
+ return weather.CurrentWeather.TemperatureCelsius + "°C";
+ }
+
+ return weather.CurrentWeather.TemperatureFahrenheit + "°F";
+ }
+ return null;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/MediaBrowser.UI/Converters/WeatherVisibilityConverter.cs b/MediaBrowser.UI/Converters/WeatherVisibilityConverter.cs
new file mode 100644
index 000000000..5706ecec9
--- /dev/null
+++ b/MediaBrowser.UI/Converters/WeatherVisibilityConverter.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace MediaBrowser.UI.Converters
+{
+ public class WeatherVisibilityConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value == null ? Visibility.Collapsed : Visibility.Visible;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/MediaBrowser.UI/MainWindow.xaml b/MediaBrowser.UI/MainWindow.xaml
new file mode 100644
index 000000000..b3c36915e
--- /dev/null
+++ b/MediaBrowser.UI/MainWindow.xaml
@@ -0,0 +1,50 @@
+<Window x:Class="MediaBrowser.UI.MainWindow"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:controls="clr-namespace:MediaBrowser.UI.Controls"
+ Title="media browser"
+ Style="{StaticResource MainWindow}"
+ WindowStartupLocation="CenterScreen"
+ AllowsTransparency="True"
+ WindowStyle="None"
+ ResizeMode="CanResizeWithGrip"
+ KeyboardNavigation.DirectionalNavigation="Contained">
+
+ <!--The window itself is a tabstop, and it can't be disabled. So this is a workaround.-->
+ <Grid>
+
+ <Grid x:Name="BackdropGrid" Style="{StaticResource BackdropGrid}">
+ </Grid>
+
+ <!--This allows the user to drag the window.-->
+ <Grid x:Name="DragBar" Style="{StaticResource DragBar}"></Grid>
+
+ <!--This allows the user to drag the window.-->
+ <controls:WindowCommands x:Name="WindowCommands" Style="{StaticResource WindowCommands}"></controls:WindowCommands>
+
+ <!--Themes will supply this template to outline the window structure.-->
+ <ContentControl x:Name="PageContent" Template="{StaticResource PageContentTemplate}"></ContentControl>
+
+ <Grid x:Name="NavBarGrid" Style="{StaticResource NavBarGrid}">
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="auto"></ColumnDefinition>
+ <ColumnDefinition Width="*"></ColumnDefinition>
+ <ColumnDefinition Width="auto"></ColumnDefinition>
+ </Grid.ColumnDefinitions>
+
+ <StackPanel Style="{StaticResource NavBarGridLeftPanel}">
+ <Button x:Name="BackButton" Style="{StaticResource BackButton}"></Button>
+ <Button x:Name="ForwardButton" Style="{StaticResource ForwardButton}"></Button>
+ </StackPanel>
+ <StackPanel Style="{StaticResource NavBarGridCenterPanel}">
+ <Button x:Name="MuteButton" Style="{StaticResource MuteButton}"></Button>
+ <Button x:Name="VolumeDownButton" Style="{StaticResource VolumeDownButton}"></Button>
+ <Button x:Name="VolumeUpButton" Style="{StaticResource VolumeUpButton}"></Button>
+ </StackPanel>
+ <StackPanel Style="{StaticResource NavBarGridRightPanel}">
+ <Button x:Name="SettingsButton" Style="{StaticResource SettingsButton}"></Button>
+ <Button x:Name="ExitButton" Style="{StaticResource ExitButton}"></Button>
+ </StackPanel>
+ </Grid>
+ </Grid>
+</Window>
diff --git a/MediaBrowser.UI/MainWindow.xaml.cs b/MediaBrowser.UI/MainWindow.xaml.cs
new file mode 100644
index 000000000..07e8e9433
--- /dev/null
+++ b/MediaBrowser.UI/MainWindow.xaml.cs
@@ -0,0 +1,368 @@
+using MediaBrowser.Model.DTO;
+using MediaBrowser.UI.Controller;
+using MediaBrowser.UI.Controls;
+using System;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media.Animation;
+using System.Windows.Media.Imaging;
+
+namespace MediaBrowser.UI
+{
+ /// <summary>
+ /// Interaction logic for MainWindow.xaml
+ /// </summary>
+ public partial class MainWindow : Window, INotifyPropertyChanged
+ {
+ private Timer MouseIdleTimer { get; set; }
+ private Timer BackdropTimer { get; set; }
+ private Image BackdropImage { get; set; }
+ private string[] CurrentBackdrops { get; set; }
+ private int CurrentBackdropIndex { get; set; }
+
+ public MainWindow()
+ {
+ InitializeComponent();
+
+ BackButton.Click += BtnApplicationBackClick;
+ ExitButton.Click += ExitButtonClick;
+ ForwardButton.Click += ForwardButtonClick;
+ DragBar.MouseDown += DragableGridMouseDown;
+ Loaded += MainWindowLoaded;
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public void OnPropertyChanged(String info)
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this, new PropertyChangedEventArgs(info));
+ }
+ }
+
+ private bool _isMouseIdle = true;
+ public bool IsMouseIdle
+ {
+ get { return _isMouseIdle; }
+ set
+ {
+ _isMouseIdle = value;
+ OnPropertyChanged("IsMouseIdle");
+ }
+ }
+
+ void MainWindowLoaded(object sender, RoutedEventArgs e)
+ {
+ DataContext = App.Instance;
+
+ if (App.Instance.ServerConfiguration == null)
+ {
+ App.Instance.PropertyChanged += ApplicationPropertyChanged;
+ }
+ else
+ {
+ LoadInitialPage();
+ }
+ }
+
+ void ForwardButtonClick(object sender, RoutedEventArgs e)
+ {
+ NavigateForward();
+ }
+
+ void ExitButtonClick(object sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+
+ void ApplicationPropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName.Equals("ServerConfiguration"))
+ {
+ App.Instance.PropertyChanged -= ApplicationPropertyChanged;
+ LoadInitialPage();
+ }
+ }
+
+ private async void LoadInitialPage()
+ {
+ await App.Instance.LogoutUser().ConfigureAwait(false);
+ }
+
+ private void DragableGridMouseDown(object sender, MouseButtonEventArgs e)
+ {
+ if (e.ClickCount == 2)
+ {
+ WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
+ }
+ else if (e.LeftButton == MouseButtonState.Pressed)
+ {
+ DragMove();
+ }
+ }
+
+ void BtnApplicationBackClick(object sender, RoutedEventArgs e)
+ {
+ NavigateBack();
+ }
+
+ private Frame PageFrame
+ {
+ get
+ {
+ // Finding the grid that is generated by the ControlTemplate of the Button
+ return TreeHelper.FindChild<Frame>(PageContent, "PageFrame");
+ }
+ }
+
+ public void Navigate(Uri uri)
+ {
+ PageFrame.Navigate(uri);
+ }
+
+ /// <summary>
+ /// Sets the backdrop based on an ApiBaseItemWrapper
+ /// </summary>
+ public void SetBackdrops(DtoBaseItem item)
+ {
+ SetBackdrops(UIKernel.Instance.ApiClient.GetBackdropImageUrls(item, null, null, 1920, 1080));
+ }
+
+ /// <summary>
+ /// Sets the backdrop based on a list of image files
+ /// </summary>
+ public async void SetBackdrops(string[] backdrops)
+ {
+ // Don't reload the same backdrops
+ if (CurrentBackdrops != null && backdrops.SequenceEqual(CurrentBackdrops))
+ {
+ return;
+ }
+
+ if (BackdropTimer != null)
+ {
+ BackdropTimer.Dispose();
+ }
+
+ BackdropGrid.Children.Clear();
+
+ if (backdrops.Length == 0)
+ {
+ CurrentBackdrops = null;
+ return;
+ }
+
+ CurrentBackdropIndex = GetFirstBackdropIndex();
+
+ Image image = await App.Instance.GetImage(backdrops.ElementAt(CurrentBackdropIndex));
+ image.SetResourceReference(Image.StyleProperty, "BackdropImage");
+
+ BackdropGrid.Children.Add(image);
+
+ CurrentBackdrops = backdrops;
+ BackdropImage = image;
+
+ const int backdropRotationTime = 7000;
+
+ if (backdrops.Count() > 1)
+ {
+ BackdropTimer = new Timer(BackdropTimerCallback, null, backdropRotationTime, backdropRotationTime);
+ }
+ }
+
+ public void ClearBackdrops()
+ {
+ if (BackdropTimer != null)
+ {
+ BackdropTimer.Dispose();
+ }
+
+ BackdropGrid.Children.Clear();
+
+ CurrentBackdrops = null;
+ }
+
+ private void BackdropTimerCallback(object stateInfo)
+ {
+ // Need to do this on the UI thread
+ Application.Current.Dispatcher.InvokeAsync(() =>
+ {
+ var animFadeOut = new Storyboard();
+ animFadeOut.Completed += AnimFadeOutCompleted;
+
+ var fadeOut = new DoubleAnimation();
+ fadeOut.From = 1.0;
+ fadeOut.To = 0.5;
+ fadeOut.Duration = new Duration(TimeSpan.FromSeconds(1));
+
+ animFadeOut.Children.Add(fadeOut);
+ Storyboard.SetTarget(fadeOut, BackdropImage);
+ Storyboard.SetTargetProperty(fadeOut, new PropertyPath(Image.OpacityProperty));
+
+ animFadeOut.Begin(this);
+ });
+ }
+
+ async void AnimFadeOutCompleted(object sender, System.EventArgs e)
+ {
+ if (CurrentBackdrops == null)
+ {
+ return;
+ }
+
+ int backdropIndex = GetNextBackdropIndex();
+
+ BitmapImage image = await App.Instance.GetBitmapImage(CurrentBackdrops[backdropIndex]);
+ CurrentBackdropIndex = backdropIndex;
+
+ // Need to do this on the UI thread
+ BackdropImage.Source = image;
+ Storyboard imageFadeIn = new Storyboard();
+
+ DoubleAnimation fadeIn = new DoubleAnimation();
+
+ fadeIn.From = 0.25;
+ fadeIn.To = 1.0;
+ fadeIn.Duration = new Duration(TimeSpan.FromSeconds(1));
+
+ imageFadeIn.Children.Add(fadeIn);
+ Storyboard.SetTarget(fadeIn, BackdropImage);
+ Storyboard.SetTargetProperty(fadeIn, new PropertyPath(Image.OpacityProperty));
+ imageFadeIn.Begin(this);
+ }
+
+ private int GetFirstBackdropIndex()
+ {
+ return 0;
+ }
+
+ private int GetNextBackdropIndex()
+ {
+ if (CurrentBackdropIndex < CurrentBackdrops.Length - 1)
+ {
+ return CurrentBackdropIndex + 1;
+ }
+
+ return 0;
+ }
+
+ public void NavigateBack()
+ {
+ if (PageFrame.NavigationService.CanGoBack)
+ {
+ PageFrame.NavigationService.GoBack();
+ }
+ }
+
+ public void NavigateForward()
+ {
+ if (PageFrame.NavigationService.CanGoForward)
+ {
+ PageFrame.NavigationService.GoForward();
+ }
+ }
+
+ /// <summary>
+ /// Shows the control bar then starts a timer to hide it
+ /// </summary>
+ private void StartMouseIdleTimer()
+ {
+ IsMouseIdle = false;
+
+ const int duration = 10000;
+
+ // Start the timer if it's null, otherwise reset it
+ if (MouseIdleTimer == null)
+ {
+ MouseIdleTimer = new Timer(MouseIdleTimerCallback, null, duration, Timeout.Infinite);
+ }
+ else
+ {
+ MouseIdleTimer.Change(duration, Timeout.Infinite);
+ }
+ }
+
+ /// <summary>
+ /// This is the Timer callback method to hide the control bar
+ /// </summary>
+ private void MouseIdleTimerCallback(object stateInfo)
+ {
+ IsMouseIdle = true;
+
+ if (MouseIdleTimer != null)
+ {
+ MouseIdleTimer.Dispose();
+ MouseIdleTimer = null;
+ }
+ }
+
+ /// <summary>
+ /// Handles OnMouseMove to show the control box
+ /// </summary>
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ base.OnMouseMove(e);
+
+ StartMouseIdleTimer();
+ }
+
+ /// <summary>
+ /// Handles OnKeyUp to provide keyboard based navigation
+ /// </summary>
+ protected override void OnKeyUp(KeyEventArgs e)
+ {
+ base.OnKeyUp(e);
+
+ if (IsBackPress(e))
+ {
+ NavigateBack();
+ }
+
+ else if (IsForwardPress(e))
+ {
+ NavigateForward();
+ }
+ }
+
+ /// <summary>
+ /// Determines if a keypress should be treated as a backward press
+ /// </summary>
+ private bool IsBackPress(KeyEventArgs e)
+ {
+ if (e.Key == Key.BrowserBack || e.Key == Key.Back)
+ {
+ return true;
+ }
+
+ if (e.SystemKey == Key.Left && e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Alt))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Determines if a keypress should be treated as a forward press
+ /// </summary>
+ private bool IsForwardPress(KeyEventArgs e)
+ {
+ if (e.Key == Key.BrowserForward)
+ {
+ return true;
+ }
+
+ if (e.SystemKey == Key.RightAlt && e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Alt))
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/MediaBrowser.UI/MediaBrowser.UI.csproj b/MediaBrowser.UI/MediaBrowser.UI.csproj
new file mode 100644
index 000000000..b099d0f83
--- /dev/null
+++ b/MediaBrowser.UI/MediaBrowser.UI.csproj
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{B5ECE1FB-618E-420B-9A99-8E972D76920A}</ProjectGuid>
+ <OutputType>WinExe</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MediaBrowser.UI</RootNamespace>
+ <AssemblyName>MediaBrowser.UI</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup>
+ <StartupObject>MediaBrowser.UI.App</StartupObject>
+ </PropertyGroup>
+ <PropertyGroup>
+ <ApplicationIcon>Resources\Images\Icon.ico</ApplicationIcon>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.ComponentModel.Composition" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Net.Http.WebRequest" />
+ <Reference Include="System.Web" />
+ <Reference Include="System.Windows.Forms" />
+ <Reference Include="System.Xml" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="System.Xaml">
+ <RequiredTargetFramework>4.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="WindowsBase" />
+ <Reference Include="PresentationCore" />
+ <Reference Include="PresentationFramework" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Configuration\UIApplicationConfiguration.cs" />
+ <Compile Include="Configuration\UIApplicationPaths.cs" />
+ <Compile Include="Controller\PluginUpdater.cs" />
+ <Compile Include="Controls\EnhancedScrollViewer.cs" />
+ <Compile Include="Controls\ExtendedImage.cs" />
+ <Compile Include="Controls\TreeHelper.cs" />
+ <Compile Include="Controls\WindowCommands.xaml.cs">
+ <DependentUpon>WindowCommands.xaml</DependentUpon>
+ </Compile>
+ <Compile Include="Converters\CurrentUserVisibilityConverter.cs" />
+ <Compile Include="Converters\DateTimeToStringConverter.cs" />
+ <Compile Include="Converters\LastSeenTextConverter.cs" />
+ <Compile Include="Converters\WeatherTemperatureConverter.cs" />
+ <Compile Include="Converters\WeatherVisibilityConverter.cs" />
+ <Compile Include="Controller\UIKernel.cs" />
+ <Compile Include="Pages\BaseLoginPage.cs" />
+ <Page Include="App.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <Page Include="Controls\WindowCommands.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <Page Include="MainWindow.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <Compile Include="App.xaml.cs">
+ <DependentUpon>App.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Pages\BasePage.cs" />
+ <Compile Include="Converters\UserImageConverter.cs" />
+ <Compile Include="MainWindow.xaml.cs">
+ <DependentUpon>MainWindow.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ <Page Include="Resources\AppResources.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:Compile</Generator>
+ </Page>
+ <Page Include="Resources\MainWindowResources.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:Compile</Generator>
+ </Page>
+ <Page Include="Resources\NavBarResources.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:Compile</Generator>
+ </Page>
+ <Page Include="Themes\Generic.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Properties\AssemblyInfo.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Properties\Resources.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>Resources.resx</DependentUpon>
+ </Compile>
+ <Compile Include="Properties\Settings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Settings.settings</DependentUpon>
+ <DesignTimeSharedInput>True</DesignTimeSharedInput>
+ </Compile>
+ <EmbeddedResource Include="Properties\Resources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <None Include="Properties\Settings.settings">
+ <Generator>SettingsSingleFileGenerator</Generator>
+ <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+ </None>
+ <AppDesigner Include="Properties\" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="App.config">
+ <SubType>Designer</SubType>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\MediaBrowserServer\MediaBrowser.ApiInteraction\MediaBrowser.ApiInteraction.csproj">
+ <Project>{921c0f64-fda7-4e9f-9e73-0cb0eedb2422}</Project>
+ <Name>MediaBrowser.ApiInteraction</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\MediaBrowserServer\MediaBrowser.Common\MediaBrowser.Common.csproj">
+ <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+ <Name>MediaBrowser.Common</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\..\MediaBrowserServer\MediaBrowser.Model\MediaBrowser.Model.csproj">
+ <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+ <Name>MediaBrowser.Model</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\BackButton.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\ForwardButton.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\ExitButton.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\SettingsButton.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\VolumeUpButton.png" />
+ <Resource Include="Resources\Images\VolumeDownButton.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\MuteButton.png" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\Icon.ico" />
+ </ItemGroup>
+ <ItemGroup>
+ <Resource Include="Resources\Images\mblogoblack.png" />
+ <Resource Include="Resources\Images\mblogowhite.png" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project> \ No newline at end of file
diff --git a/MediaBrowser.UI/Pages/BaseLoginPage.cs b/MediaBrowser.UI/Pages/BaseLoginPage.cs
new file mode 100644
index 000000000..cd3151df0
--- /dev/null
+++ b/MediaBrowser.UI/Pages/BaseLoginPage.cs
@@ -0,0 +1,33 @@
+using MediaBrowser.Model.DTO;
+using MediaBrowser.UI.Controller;
+using System;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.UI.Pages
+{
+ public class BaseLoginPage : BasePage
+ {
+ private DtoUser[] _users;
+ public DtoUser[] Users
+ {
+ get { return _users; }
+
+ set
+ {
+ _users = value;
+ OnPropertyChanged("Users");
+ }
+ }
+
+ protected override async Task LoadData()
+ {
+ Users = await UIKernel.Instance.ApiClient.GetAllUsersAsync().ConfigureAwait(false);
+ }
+
+ protected void UserClicked(DtoUser user)
+ {
+ App.Instance.CurrentUser = user;
+ //App.Instance.Navigate(new Uri("/Pages/HomePage.xaml", UriKind.Relative));
+ }
+ }
+}
diff --git a/MediaBrowser.UI/Pages/BasePage.cs b/MediaBrowser.UI/Pages/BasePage.cs
new file mode 100644
index 000000000..800f6e215
--- /dev/null
+++ b/MediaBrowser.UI/Pages/BasePage.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Threading.Tasks;
+using System.Web;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace MediaBrowser.UI.Pages
+{
+ public abstract class BasePage : Page, INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public void OnPropertyChanged(String info)
+ {
+ if (PropertyChanged != null)
+ {
+ PropertyChanged(this, new PropertyChangedEventArgs(info));
+ }
+ }
+
+ protected Uri Uri
+ {
+ get
+ {
+ return NavigationService.CurrentSource;
+ }
+ }
+
+ protected MainWindow MainWindow
+ {
+ get
+ {
+ return App.Instance.MainWindow as MainWindow;
+ }
+ }
+
+ private NameValueCollection _queryString;
+ protected NameValueCollection QueryString
+ {
+ get
+ {
+ if (_queryString == null)
+ {
+ string url = Uri.ToString();
+
+ int index = url.IndexOf('?');
+
+ if (index == -1)
+ {
+ _queryString = new NameValueCollection();
+ }
+ else
+ {
+ _queryString = HttpUtility.ParseQueryString(url.Substring(index + 1));
+ }
+ }
+
+ return _queryString;
+ }
+ }
+
+ protected BasePage()
+ : base()
+ {
+ Loaded += BasePageLoaded;
+ }
+
+ async void BasePageLoaded(object sender, RoutedEventArgs e)
+ {
+ await LoadData();
+
+ DataContext = this;
+ }
+
+ protected abstract Task LoadData();
+ }
+}
diff --git a/MediaBrowser.UI/Properties/AssemblyInfo.cs b/MediaBrowser.UI/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..565b1801e
--- /dev/null
+++ b/MediaBrowser.UI/Properties/AssemblyInfo.cs
@@ -0,0 +1,53 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("MediaBrowser.UI")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.UI")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+//In order to begin building localizable applications, set
+//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
+//inside a <PropertyGroup>. For example, if you are using US english
+//in your source files, set the <UICulture> to en-US. Then uncomment
+//the NeutralResourceLanguage attribute below. Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
+
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/MediaBrowser.UI/Properties/Resources.Designer.cs b/MediaBrowser.UI/Properties/Resources.Designer.cs
new file mode 100644
index 000000000..b9d742620
--- /dev/null
+++ b/MediaBrowser.UI/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.17626
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace MediaBrowser.UI.Properties
+{
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MediaBrowser.UI.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.UI/Properties/Resources.resx b/MediaBrowser.UI/Properties/Resources.resx
new file mode 100644
index 000000000..ffecec851
--- /dev/null
+++ b/MediaBrowser.UI/Properties/Resources.resx
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root> \ No newline at end of file
diff --git a/MediaBrowser.UI/Properties/Settings.Designer.cs b/MediaBrowser.UI/Properties/Settings.Designer.cs
new file mode 100644
index 000000000..4d9ddf50d
--- /dev/null
+++ b/MediaBrowser.UI/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.17626
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace MediaBrowser.UI.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.UI/Properties/Settings.settings b/MediaBrowser.UI/Properties/Settings.settings
new file mode 100644
index 000000000..8f2fd95d6
--- /dev/null
+++ b/MediaBrowser.UI/Properties/Settings.settings
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+ <Profiles>
+ <Profile Name="(Default)" />
+ </Profiles>
+ <Settings />
+</SettingsFile> \ No newline at end of file
diff --git a/MediaBrowser.UI/Resources/AppResources.xaml b/MediaBrowser.UI/Resources/AppResources.xaml
new file mode 100644
index 000000000..8d4f36d4f
--- /dev/null
+++ b/MediaBrowser.UI/Resources/AppResources.xaml
@@ -0,0 +1,122 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:System="clr-namespace:System;assembly=mscorlib"
+ xmlns:converters="clr-namespace:MediaBrowser.UI.Converters">
+
+ <!--Themes should override these as needed-->
+ <FontFamily x:Key="DefaultFontFamily">Segoe UI, Lucida Sans Unicode, Verdana</FontFamily>
+ <FontWeight x:Key="DefaultFontWeight">Thin</FontWeight>
+ <Brush x:Key="DefaultForeground">Black</Brush>
+ <System:Double x:Key="DefaultFontSize">36</System:Double>
+ <System:Double x:Key="Heading1FontSize">84</System:Double>
+ <System:Double x:Key="Heading2FontSize">60</System:Double>
+
+ <!--Define all the standard converters here in one place-->
+ <converters:DateTimeToStringConverter x:Key="DateTimeToStringConverter"></converters:DateTimeToStringConverter>
+ <converters:UserImageConverter x:Key="UserImageConverter"></converters:UserImageConverter>
+ <converters:WeatherTemperatureConverter x:Key="WeatherTemperatureConverter"></converters:WeatherTemperatureConverter>
+ <converters:LastSeenTextConverter x:Key="LastSeenTextConverter"></converters:LastSeenTextConverter>
+ <converters:WeatherVisibilityConverter x:Key="WeatherVisibilityConverter"></converters:WeatherVisibilityConverter>
+ <converters:CurrentUserVisibilityConverter x:Key="CurrentUserVisibilityConverter"></converters:CurrentUserVisibilityConverter>
+
+ <!--Default Frame style. -->
+ <Style TargetType="Frame">
+ <Setter Property="NavigationUIVisibility" Value="Hidden"/>
+ <Setter Property="KeyboardNavigation.IsTabStop" Value="false"/>
+ </Style>
+
+ <!--Default Frame style. -->
+ <Style TargetType="ContentControl">
+ <Setter Property="KeyboardNavigation.IsTabStop" Value="false"/>
+ </Style>
+
+ <!--Default Window style. -->
+ <Style TargetType="Window" x:Key="BaseWindow">
+ <Setter Property="FontSize" Value="{StaticResource DefaultFontSize}"/>
+ <Setter Property="FontFamily" Value="{StaticResource DefaultFontFamily}"/>
+ <Setter Property="FontWeight" Value="{StaticResource DefaultFontWeight}"/>
+ <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+ <Setter Property="BorderBrush" Value="#cccccc"/>
+ <Setter Property="BorderThickness" Value="1"/>
+ </Style>
+
+ <!--Default TextBlock style. -->
+ <Style TargetType="TextBlock">
+ <Setter Property="FontSize" Value="{StaticResource DefaultFontSize}"/>
+ <Setter Property="FontFamily" Value="{StaticResource DefaultFontFamily}"/>
+ <Setter Property="FontWeight" Value="{StaticResource DefaultFontWeight}"/>
+ <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+ <Setter Property="TextWrapping" Value="Wrap" />
+ </Style>
+
+ <!--Default Label style. -->
+ <Style TargetType="Label">
+ <Setter Property="FontSize" Value="{StaticResource DefaultFontSize}"/>
+ <Setter Property="FontFamily" Value="{StaticResource DefaultFontFamily}"/>
+ <Setter Property="FontWeight" Value="{StaticResource DefaultFontWeight}"/>
+ <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+ </Style>
+
+ <!--Default Button style. -->
+ <Style TargetType="Button">
+ <Setter Property="FontSize" Value="{StaticResource DefaultFontSize}"/>
+ <Setter Property="FontFamily" Value="{StaticResource DefaultFontFamily}"/>
+ <Setter Property="FontWeight" Value="{StaticResource DefaultFontWeight}"/>
+ <Setter Property="Foreground" Value="{StaticResource DefaultForeground}"/>
+ </Style>
+
+ <!--Default style for buttons that have images. -->
+ <Style TargetType="Button" x:Key="ImageButton" BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}">
+ <Setter Property="Margin" Value="0"/>
+ <Setter Property="Padding" Value="0"/>
+ <Setter Property="BorderThickness" Value="0"/>
+ <Setter Property="Cursor" Value="Hand"/>
+ <Style.Triggers>
+ <Trigger Property="IsMouseOver" Value="True">
+ <Setter Property="Opacity" Value=".5" />
+ </Trigger>
+ </Style.Triggers>
+ </Style>
+
+ <!--Default ListViewItem style. -->
+ <Style x:Key="BaseListViewItemStyle" TargetType="{x:Type ListViewItem}">
+
+ <Setter Property="Padding" Value="0" />
+ <Setter Property="Margin" Value="0" />
+ <Setter Property="Cursor" Value="Hand"/>
+
+ <Style.Triggers>
+ <Trigger Property="IsKeyboardFocusWithin" Value="True">
+ <Setter Property="IsSelected" Value="True" />
+ </Trigger>
+ </Style.Triggers>
+ </Style>
+
+ <!--Themes should override this -->
+ <Style x:Key="ListViewItemStyle" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource BaseListViewItemStyle}">
+ </Style>
+
+ <!--Default ListView style. -->
+ <Style TargetType="ListView" x:Key="BaseListViewStyle">
+ <Setter Property="BorderThickness" Value="0"/>
+ <Setter Property="Background" Value="Transparent"/>
+ <Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
+ <Setter Property="KeyboardNavigation.DirectionalNavigation" Value="Continue"/>
+ <Setter Property="VirtualizingPanel.IsVirtualizing" Value="True"/>
+ <Setter Property="IsSynchronizedWithCurrentItem" Value="True"/>
+ </Style>
+
+ <!--Themes should override this -->
+ <Style x:Key="ListViewStyle" TargetType="{x:Type ListView}" BasedOn="{StaticResource BaseListViewStyle}">
+ </Style>
+
+ <!--MB Logo, black text. -->
+ <Style TargetType="Image" x:Key="MBLogoImageBlack">
+ <Setter Property="Source" Value="Images/mblogoblack.png"/>
+ </Style>
+
+ <!--MB Logo, white text. -->
+ <Style TargetType="Image" x:Key="MBLogoImageWhite">
+ <Setter Property="Source" Value="Images/mblogowhite.png"/>
+ </Style>
+</ResourceDictionary> \ No newline at end of file
diff --git a/MediaBrowser.UI/Resources/Images/BackButton.png b/MediaBrowser.UI/Resources/Images/BackButton.png
new file mode 100644
index 000000000..263eceadb
--- /dev/null
+++ b/MediaBrowser.UI/Resources/Images/BackButton.png
Binary files differ
diff --git a/MediaBrowser.UI/Resources/Images/ExitButton.png b/MediaBrowser.UI/Resources/Images/ExitButton.png
new file mode 100644
index 000000000..c7d5c0f76
--- /dev/null
+++ b/MediaBrowser.UI/Resources/Images/ExitButton.png
Binary files differ
diff --git a/MediaBrowser.UI/Resources/Images/ForwardButton.png b/MediaBrowser.UI/Resources/Images/ForwardButton.png
new file mode 100644
index 000000000..a9548b309
--- /dev/null
+++ b/MediaBrowser.UI/Resources/Images/ForwardButton.png
Binary files differ
diff --git a/MediaBrowser.UI/Resources/Images/Icon.ico b/MediaBrowser.UI/Resources/Images/Icon.ico
new file mode 100644
index 000000000..f8accfab2
--- /dev/null
+++ b/MediaBrowser.UI/Resources/Images/Icon.ico
Binary files differ
diff --git a/MediaBrowser.UI/Resources/Images/MuteButton.png b/MediaBrowser.UI/Resources/Images/MuteButton.png
new file mode 100644
index 000000000..fa454b8f3
--- /dev/null
+++ b/MediaBrowser.UI/Resources/Images/MuteButton.png
Binary files differ
diff --git a/MediaBrowser.UI/Resources/Images/SettingsButton.png b/MediaBrowser.UI/Resources/Images/SettingsButton.png
new file mode 100644
index 000000000..04ca4d32b
--- /dev/null
+++ b/MediaBrowser.UI/Resources/Images/SettingsButton.png
Binary files differ
diff --git a/MediaBrowser.UI/Resources/Images/VolumeDownButton.png b/MediaBrowser.UI/Resources/Images/VolumeDownButton.png
new file mode 100644
index 000000000..c7ff252ce
--- /dev/null
+++ b/MediaBrowser.UI/Resources/Images/VolumeDownButton.png
Binary files differ
diff --git a/MediaBrowser.UI/Resources/Images/VolumeUpButton.png b/MediaBrowser.UI/Resources/Images/VolumeUpButton.png
new file mode 100644
index 000000000..c89d25691
--- /dev/null
+++ b/MediaBrowser.UI/Resources/Images/VolumeUpButton.png
Binary files differ
diff --git a/MediaBrowser.UI/Resources/Images/mblogoblack.png b/MediaBrowser.UI/Resources/Images/mblogoblack.png
new file mode 100644
index 000000000..84323fe52
--- /dev/null
+++ b/MediaBrowser.UI/Resources/Images/mblogoblack.png
Binary files differ
diff --git a/MediaBrowser.UI/Resources/Images/mblogowhite.png b/MediaBrowser.UI/Resources/Images/mblogowhite.png
new file mode 100644
index 000000000..a39812e35
--- /dev/null
+++ b/MediaBrowser.UI/Resources/Images/mblogowhite.png
Binary files differ
diff --git a/MediaBrowser.UI/Resources/MainWindowResources.xaml b/MediaBrowser.UI/Resources/MainWindowResources.xaml
new file mode 100644
index 000000000..624e7a633
--- /dev/null
+++ b/MediaBrowser.UI/Resources/MainWindowResources.xaml
@@ -0,0 +1,43 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+
+ <!--Themes should override this to style the window-->
+ <Style TargetType="Window" x:Key="MainWindow" BasedOn="{StaticResource BaseWindow}">
+ </Style>
+
+ <!--Themes may want to override this to adjust the backdrop container style-->
+ <Style TargetType="Grid" x:Key="BackdropGrid">
+ <Setter Property="Background" Value="Transparent"/>
+ <Setter Property="Opacity" Value=".15"/>
+ </Style>
+
+ <!--Themes may want to override this to adjust the backdrop image style-->
+ <Style TargetType="Image" x:Key="BackdropImage">
+ <Setter Property="Stretch" Value="UniformToFill"/>
+ </Style>
+
+ <Style TargetType="Grid" x:Key="DragBar">
+ <Setter Property="Background" Value="Transparent"/>
+ <Setter Property="Height" Value="50"/>
+ <Setter Property="VerticalAlignment" Value="Top"/>
+ <Setter Property="Panel.ZIndex" Value="1"/>
+ </Style>
+ <Style TargetType="UserControl" x:Key="WindowCommands">
+ <Setter Property="Margin" Value="0 10 0 0"/>
+ <Setter Property="HorizontalAlignment" Value="Right"/>
+ <Setter Property="VerticalAlignment" Value="Top"/>
+ <Setter Property="Panel.ZIndex" Value="2"/>
+ <Setter Property="Visibility" Value="Collapsed" />
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding Path=MainWindow.IsMouseIdle}" Value="false">
+ <Setter Property="Visibility" Value="Visible" />
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+
+ <!--Themes should override this to layout window content-->
+ <ControlTemplate x:Key="PageContentTemplate">
+ <Frame x:Name="PageFrame"></Frame>
+ </ControlTemplate>
+
+</ResourceDictionary> \ No newline at end of file
diff --git a/MediaBrowser.UI/Resources/NavBarResources.xaml b/MediaBrowser.UI/Resources/NavBarResources.xaml
new file mode 100644
index 000000000..c2181c16f
--- /dev/null
+++ b/MediaBrowser.UI/Resources/NavBarResources.xaml
@@ -0,0 +1,122 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+ <Style TargetType="Button" x:Key="NavBarButton" BasedOn="{StaticResource ImageButton}">
+ <Setter Property="Margin" Value="10 0 10 0"/>
+ <Setter Property="VerticalAlignment" Value="Center"/>
+ <Setter Property="HorizontalAlignment" Value="Left"/>
+ <Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
+ </Style>
+
+ <Style TargetType="Button" x:Key="BackButton" BasedOn="{StaticResource NavBarButton}">
+ <Setter Property="ToolTip" Value="Back"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <Image x:Name="img" Source="..\Resources\Images\BackButton.png" Stretch="None" />
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Button" x:Key="ForwardButton" BasedOn="{StaticResource NavBarButton}">
+ <Setter Property="ToolTip" Value="Forward"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <Image x:Name="img" Source="..\Resources\Images\ForwardButton.png" Stretch="None" />
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Button" x:Key="ExitButton" BasedOn="{StaticResource NavBarButton}">
+ <Setter Property="ToolTip" Value="Exit"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <Image x:Name="img" Source="..\Resources\Images\ExitButton.png" Stretch="None" />
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Button" x:Key="SettingsButton" BasedOn="{StaticResource NavBarButton}">
+ <Setter Property="ToolTip" Value="Settings"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <Image x:Name="img" Source="..\Resources\Images\SettingsButton.png" Stretch="None" />
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Button" x:Key="VolumeUpButton" BasedOn="{StaticResource NavBarButton}">
+ <Setter Property="ToolTip" Value="Increase Volume"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <Image x:Name="img" Source="..\Resources\Images\VolumeUpButton.png" Stretch="None" />
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Button" x:Key="VolumeDownButton" BasedOn="{StaticResource NavBarButton}">
+ <Setter Property="ToolTip" Value="Decrease Volume"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <Image x:Name="img" Source="..\Resources\Images\VolumeDownButton.png" Stretch="None" />
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Button" x:Key="MuteButton" BasedOn="{StaticResource NavBarButton}">
+ <Setter Property="ToolTip" Value="Mute"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate>
+ <Image x:Name="img" Source="..\Resources\Images\MuteButton.png" Stretch="None" />
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <Style TargetType="Grid" x:Key="NavBarGrid">
+ <Setter Property="VerticalAlignment" Value="Bottom"/>
+ <Setter Property="Background">
+ <Setter.Value>
+ <LinearGradientBrush StartPoint="0,0" EndPoint="0,1" Opacity=".8">
+ <GradientStop Color="#333333" Offset="0.0"/>
+ <GradientStop Color="Black" Offset="1.0"/>
+ </LinearGradientBrush>
+ </Setter.Value>
+ </Setter>
+ <Setter Property="Visibility" Value="Collapsed" />
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding Path=MainWindow.IsMouseIdle}" Value="false">
+ <Setter Property="Visibility" Value="Visible" />
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ <Style TargetType="StackPanel" x:Key="NavBarGridLeftPanel">
+ <Setter Property="Grid.Column" Value="0"/>
+ <Setter Property="HorizontalAlignment" Value="Left"/>
+ <Setter Property="Orientation" Value="Horizontal"/>
+ <Setter Property="Margin" Value="15 20 15 20"/>
+ </Style>
+ <Style TargetType="StackPanel" x:Key="NavBarGridCenterPanel">
+ <Setter Property="Grid.Column" Value="1"/>
+ <Setter Property="HorizontalAlignment" Value="Center"/>
+ <Setter Property="Orientation" Value="Horizontal"/>
+ <Setter Property="Margin" Value="15 20 15 20"/>
+ </Style>
+ <Style TargetType="StackPanel" x:Key="NavBarGridRightPanel">
+ <Setter Property="Grid.Column" Value="2"/>
+ <Setter Property="HorizontalAlignment" Value="Right"/>
+ <Setter Property="Orientation" Value="Horizontal"/>
+ <Setter Property="Margin" Value="15 20 15 20"/>
+ </Style>
+</ResourceDictionary> \ No newline at end of file
diff --git a/MediaBrowser.UI/Themes/Generic.xaml b/MediaBrowser.UI/Themes/Generic.xaml
new file mode 100644
index 000000000..c34489b4e
--- /dev/null
+++ b/MediaBrowser.UI/Themes/Generic.xaml
@@ -0,0 +1,32 @@
+<ResourceDictionary
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:local="clr-namespace:MediaBrowser.UI.Controls">
+
+ <Style TargetType="{x:Type local:ExtendedImage}">
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type local:ExtendedImage}">
+ <Border Background="{TemplateBinding Background}">
+ <Image x:Name="theImage">
+ <Image.Style>
+ <Style TargetType="{x:Type Image}">
+ <Setter Property="Source"
+ Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PlaceHolderSource}" />
+ <Setter Property="Stretch"
+ Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path= Stretch}" />
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=HasImage}" Value="True">
+ <Setter Property="Source"
+ Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Source}" />
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ </Image.Style>
+ </Image>
+ </Border>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+</ResourceDictionary>
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index b5b24ce61..7f1015591 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -58,7 +58,7 @@
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
- <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\" /y</PostBuildEvent>
+ <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData-Server\Plugins\" /y</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.