Adding the UI to the same repo. Made some default theme progress

This commit is contained in:
LukePulverenti Luke Pulverenti luke pulverenti 2012-09-20 11:25:22 -04:00
parent d8c01ded6e
commit 119dfc3ac7
70 changed files with 3384 additions and 30 deletions

View File

@ -29,6 +29,8 @@ syntax: glob
obj/
[Rr]elease*/
ProgramData*/
ProgramData-Server*/
ProgramData-UI*/
_ReSharper*/
[Tt]humbs.db
[Tt]est[Rr]esult*

View File

@ -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;
}
}
}

View File

@ -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];
}
}
}

View File

@ -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.

View File

@ -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>

View File

@ -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" />

View File

@ -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));
}
}
}

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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.

View File

@ -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>

View File

@ -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();
}
}
}

View File

@ -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"); }
}
}
}

View File

@ -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();
}
}
}

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -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" />

View File

@ -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

View File

@ -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>

14
MediaBrowser.UI/App.xaml Normal file
View File

@ -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>

213
MediaBrowser.UI/App.xaml.cs Normal file
View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,8 @@
using MediaBrowser.Common.Kernel;
namespace MediaBrowser.UI.Configuration
{
public class UIApplicationPaths : BaseApplicationPaths
{
}
}

View File

@ -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; }
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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)));
}
}
}

View File

@ -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
}
}

View File

@ -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>

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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));
}
}
}

View File

@ -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();
}
}

View File

@ -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")]

View File

@ -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;
}
}
}
}

View File

@ -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>

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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.