Discord Plugin Source

Added discord plugin source to github
This commit is contained in:
Brett Hewitson 2020-07-11 13:09:27 +10:00
parent 4a163627b8
commit 6f671a9d57
50 changed files with 3673 additions and 0 deletions

View file

@ -0,0 +1,228 @@
<Window x:Class="ServerManagerTool.Plugin.Discord.Windows.ConfigProfileWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:pc="clr-namespace:ServerManagerTool.Plugin.Common;assembly=ServerManager.Plugin.Common"
Title="{DynamicResource ConfigProfileWindow_Title}"
Icon="/ServerManager.Plugin.Discord;component/Art/favicon.ico"
Width="640" Height="520" MinWidth="640" MinHeight="480" ResizeMode="CanResizeWithGrip" WindowStyle="ToolWindow" WindowStartupLocation="CenterOwner" ShowInTaskbar="False" Closing="ConfigProfileWindow_Closing">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/ServerManager.Plugin.Discord;component/Globalization/en-US/en-US.xaml"/>
</ResourceDictionary.MergedDictionaries>
<SolidColorBrush x:Key="BeigeBorder" Color="#FFD8CCBC"/>
<LinearGradientBrush x:Key="BeigeGradient" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFECE1D4" Offset="1"/>
<GradientStop Color="#FFEAE8E6"/>
</LinearGradientBrush>
<ObjectDataProvider x:Key="AlertTypes" MethodName="GetValues" ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="pc:AlertType" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<Style x:Key="GroupBoxStyle" TargetType="GroupBox" BasedOn="{StaticResource {x:Type GroupBox}}">
<Setter Property="BorderBrush" Value="{StaticResource BeigeBorder}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="Transparent"/>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid Background="{StaticResource BeigeGradient}">
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Margin="5" ScrollViewer.VerticalScrollBarVisibility="Auto">
<Grid DockPanel.Dock="Top">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="100"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource ConfigProfileWindow_NameLabel}" ToolTip="{DynamicResource ConfigProfileWindow_NameTooltip}"/>
<TextBox Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Margin="1" Text="{Binding Profile.Name}" ToolTip="{DynamicResource ConfigProfileWindow_NameTooltip}" VerticalContentAlignment="Center" />
<CheckBox Grid.Row="0" Grid.Column="3" Margin="5,2,5,2" Content="{DynamicResource ConfigProfileWindow_IsEnabledLabel}" IsChecked="{Binding Profile.IsEnabled}" ToolTip="{DynamicResource ConfigProfileWindow_IsEnabledTooltip}" VerticalAlignment="Center" HorizontalAlignment="Left"/>
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource ConfigProfileWindow_WebhookLabel}" ToolTip="{DynamicResource ConfigProfileWindow_WebhookTooltip}"/>
<TextBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="1" Text="{Binding Profile.DiscordWebhookUrl}" ToolTip="{DynamicResource ConfigProfileWindow_WebhookTooltip}" VerticalContentAlignment="Center" />
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource ConfigProfileWindow_BotNameLabel}" ToolTip="{DynamicResource ConfigProfileWindow_BotNameTooltip}"/>
<TextBox Grid.Row="2" Grid.Column="1" Margin="1" Text="{Binding Profile.DiscordBotName}" ToolTip="{DynamicResource ConfigProfileWindow_BotNameTooltip}" VerticalContentAlignment="Center" />
</Grid>
<GroupBox DockPanel.Dock="Top" HorizontalAlignment="Stretch" Style="{StaticResource GroupBoxStyle}">
<GroupBox.Header>
<StackPanel Orientation="Horizontal">
<Label Content="{DynamicResource ConfigProfileWindow_MessageOptionsLabel}"/>
</StackPanel>
</GroupBox.Header>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Row="0" Grid.Column="0" Margin="0,2,20,2" Content="{DynamicResource ConfigProfileWindow_PrefixMessageWithProfileNameLabel}" IsChecked="{Binding Profile.PrefixMessageWithProfileName}" ToolTip="{DynamicResource ConfigProfileWindow_PrefixMessageWithProfileNameTooltip}" VerticalAlignment="Center" HorizontalAlignment="Left"/>
<CheckBox Grid.Row="0" Grid.Column="1" Margin="10,2,20,2" Content="{DynamicResource ConfigProfileWindow_UseTTSLabel}" IsChecked="{Binding Profile.DiscordUseTTS}" ToolTip="{DynamicResource ConfigProfileWindow_UseTTSTooltip}" VerticalAlignment="Center" HorizontalAlignment="Left"/>
<CheckBox Grid.Row="1" Grid.Column="0" Margin="0,2,20,2" Content="{DynamicResource ConfigProfileWindow_MessageOptionsBoldLabel}" IsChecked="{Binding Profile.MessageBold}" ToolTip="{DynamicResource ConfigProfileWindow_MessageOptionsBoldTooltip}" VerticalAlignment="Center" HorizontalAlignment="Left"/>
<CheckBox Grid.Row="1" Grid.Column="1" Margin="10,2,20,2" Content="{DynamicResource ConfigProfileWindow_MessageOptionsUnderlineLabel}" IsChecked="{Binding Profile.MessageUnderlined}" ToolTip="{DynamicResource ConfigProfileWindow_MessageOptionsUnderlineTooltip}" VerticalAlignment="Center" HorizontalAlignment="Left"/>
<CheckBox Grid.Row="2" Grid.Column="0" Margin="0,2,20,2" Content="{DynamicResource ConfigProfileWindow_MessageOptionsItalicLabel}" IsChecked="{Binding Profile.MessageItalic}" ToolTip="{DynamicResource ConfigProfileWindow_MessageOptionsItalicTooltip}" VerticalAlignment="Center" HorizontalAlignment="Left"/>
<CheckBox Grid.Row="2" Grid.Column="1" Margin="10,2,20,2" Content="{DynamicResource ConfigProfileWindow_MessageOptionsCodeBlockLabel}" IsChecked="{Binding Profile.MessageCodeBlock}" ToolTip="{DynamicResource ConfigProfileWindow_MessageOptionsCodeBlockTooltip}" VerticalAlignment="Center" HorizontalAlignment="Left"/>
</Grid>
</GroupBox>
<Grid DockPanel.Dock="Bottom" Margin="0,0,10,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="{DynamicResource ConfigProfileWindow_TestButtonLabel}" Margin="5" MinWidth="75" Click="Test_Click"/>
<Button Grid.Column="2" Content="{DynamicResource Global_OkButtonLabel}" Margin="5" MinWidth="75" HorizontalAlignment="Right" Click="Ok_Click"/>
<Button Grid.Column="3" Content="{DynamicResource Global_CancelButtonLabel}" Margin="5" MinWidth="75" HorizontalAlignment="Left" IsCancel="True"/>
</Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="10"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<GroupBox Grid.Row="1" Grid.Column="0" HorizontalAlignment="Stretch" Style="{StaticResource GroupBoxStyle}">
<GroupBox.Header>
<StackPanel Orientation="Horizontal">
<Label Content="{DynamicResource ConfigProfileWindow_ProfileNamesLabel}"/>
<Button Width="22" Height="22" Click="AddProfileName_Click" Margin="20,0,0,0" ToolTip="{DynamicResource ConfigProfileWindow_AddProfileNameTooltip}">
<Image Source="{pc:Icon Path=/ServerManager.Plugin.Discord;component/Art/Add.ico,Size=32}" />
</Button>
<Button Width="22" Height="22" Click="ClearProfileNames_Click" Margin="10,0,0,0" ToolTip="{DynamicResource ConfigProfileWindow_ClearProfileNamesTooltip}">
<Image Source="{pc:Icon Path=/ServerManager.Plugin.Discord;component/Art/Delete.ico,Size=32}" />
</Button>
</StackPanel>
</GroupBox.Header>
<DataGrid ItemsSource="{Binding Profile.ProfileNames}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" CanUserReorderColumns="False" CanUserSortColumns="true" SelectionMode="Single" CanUserResizeColumns="False" CanUserResizeRows="False" RowHeaderWidth="25">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="{x:Static SystemColors.HighlightColor}"/>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}" Color="{x:Static SystemColors.HighlightTextColor}"/>
</Style.Resources>
</Style>
</DataGrid.Resources>
<DataGrid.HorizontalGridLinesBrush>
<SolidColorBrush Color="#FFB4B4B4"/>
</DataGrid.HorizontalGridLinesBrush>
<DataGrid.VerticalGridLinesBrush>
<SolidColorBrush Color="#FFB4B4B4"/>
</DataGrid.VerticalGridLinesBrush>
<DataGrid.Columns>
<DataGridTextColumn Width="*" Binding="{Binding Value}">
<DataGridTextColumn.Header>
<TextBlock Text="{DynamicResource ConfigProfileWindow_ProfileNameColumnLabel}"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTemplateColumn Width="30" CanUserReorder="False" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Width="22" Height="22" Margin="0" IsTabStop="False" HorizontalAlignment="Center" VerticalAlignment="Center" Click="DeleteProfileName_Click" ToolTip="{DynamicResource ConfigProfileWindow_DeleteProfileNameTooltip}">
<Image Source="{pc:Icon Path=/ServerManager.Plugin.Discord;component/Art/Delete.ico,Size=32}" />
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<GroupBox Grid.Row="1" Grid.Column="2" HorizontalAlignment="Stretch" Style="{StaticResource GroupBoxStyle}">
<GroupBox.Header>
<StackPanel Orientation="Horizontal">
<Label Content="{DynamicResource ConfigProfileWindow_AlertTypesLabel}"/>
<Button Width="22" Height="22" Click="AddAlertType_Click" Margin="20,0,0,0" ToolTip="{DynamicResource ConfigProfileWindow_AddAlertTypeTooltip}">
<Image Source="{pc:Icon Path=/ServerManager.Plugin.Discord;component/Art/Add.ico,Size=32}" />
</Button>
<Button Width="22" Height="22" Click="ClearAlertTypes_Click" Margin="10,0,0,0" ToolTip="{DynamicResource ConfigProfileWindow_ClearAlertTypesTooltip}">
<Image Source="{pc:Icon Path=/ServerManager.Plugin.Discord;component/Art/Delete.ico,Size=32}" />
</Button>
</StackPanel>
</GroupBox.Header>
<DataGrid ItemsSource="{Binding Profile.AlertTypes}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" CanUserReorderColumns="False" CanUserSortColumns="true" SelectionMode="Single" CanUserResizeColumns="False" CanUserResizeRows="False" RowHeaderWidth="25">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="{x:Static SystemColors.HighlightColor}"/>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}" Color="{x:Static SystemColors.HighlightTextColor}"/>
</Style.Resources>
</Style>
</DataGrid.Resources>
<DataGrid.HorizontalGridLinesBrush>
<SolidColorBrush Color="#FFB4B4B4"/>
</DataGrid.HorizontalGridLinesBrush>
<DataGrid.VerticalGridLinesBrush>
<SolidColorBrush Color="#FFB4B4B4"/>
</DataGrid.VerticalGridLinesBrush>
<DataGrid.Columns>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.Header>
<TextBlock Text="{DynamicResource ConfigProfileWindow_AlertTypeColumnLabel}"/>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox IsEditable="False" ItemsSource="{Binding Source={StaticResource AlertTypes}}" SelectedValue="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" PreviewMouseWheel="ComboBox_PreviewMouseWheel"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="30" CanUserReorder="False" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Width="22" Height="22" Margin="0" IsTabStop="False" HorizontalAlignment="Center" VerticalAlignment="Center" Click="DeleteAlertType_Click" ToolTip="{DynamicResource ConfigProfileWindow_DeleteAlertTypeTooltip}">
<Image Source="{pc:Icon Path=/ServerManager.Plugin.Discord;component/Art/Delete.ico,Size=32}" />
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</GroupBox>
</Grid>
</DockPanel>
</Grid>
</Window>

View file

@ -0,0 +1,223 @@
using ServerManagerTool.Plugin.Common;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace ServerManagerTool.Plugin.Discord.Windows
{
/// <summary>
/// Interaction logic for ConfigProfileWindow.xaml
/// </summary>
public partial class ConfigProfileWindow : Window
{
private static readonly DependencyProperty ProfileProperty = DependencyProperty.Register(nameof(Profile), typeof(ConfigProfile), typeof(ConfigProfileWindow));
internal ConfigProfileWindow(DiscordPlugin plugin, ConfigProfile profile)
{
this.Plugin = plugin ?? new DiscordPlugin();
this.OriginalProfile = profile;
this.Profile = profile.Clone();
this.Profile.CommitChanges();
InitializeComponent();
if (plugin.BetaEnabled)
Title = $"{Title} {ResourceUtils.GetResourceString(this.Resources, "Global_BetaModeLabel")}";
this.DataContext = this;
}
private ConfigProfile OriginalProfile
{
get;
set;
}
private ConfigProfile Profile
{
get { return GetValue(ProfileProperty) as ConfigProfile; }
set { SetValue(ProfileProperty, value); }
}
private DiscordPlugin Plugin
{
get;
set;
}
private void ConfigProfileWindow_Closing(object sender, CancelEventArgs e)
{
if (DialogResult.HasValue && DialogResult.Value)
return;
if (this.Profile.HasAnyChanges)
{
if (MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_CloseLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_CloseTitle"), MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
e.Cancel = true;
}
}
private void ComboBox_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var comboBox = sender as ComboBox;
if (comboBox == null)
return;
if (comboBox.IsDropDownOpen)
return;
e.Handled = true;
}
private void Ok_Click(object sender, RoutedEventArgs e)
{
if (this.Profile.HasAnyChanges)
{
this.OriginalProfile.CopyFrom(this.Profile);
}
DialogResult = true;
Close();
}
private void Test_Click(object sender, RoutedEventArgs e)
{
if (!Profile.IsEnabled)
{
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_TestEnabledErrorLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_TestErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
try
{
foreach (var profileName in Profile.ProfileNames)
{
foreach (var alertType in Profile.AlertTypes)
{
Plugin.HandleAlert(Profile, alertType.Value, profileName.Value, $"Test '{alertType.Value}' message for profile name '{profileName.Value}'.");
Task.Delay(1000).Wait();
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(Test_Click)}\r\n{ex.Message}");
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_TestErrorLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_TestErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void AddAlertType_Click(object sender, RoutedEventArgs e)
{
try
{
var alertType = new AlertTypeValue(AlertType.Error);
Profile.AlertTypes.Add(alertType);
Profile.AlertTypes.NotifyAdd(alertType);
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(AddAlertType_Click)}\r\n{ex.Message}");
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_AddAlertTypeErrorLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_AddErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ClearAlertTypes_Click(object sender, RoutedEventArgs e)
{
if (MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_ClearLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_ClearTitle"), MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
return;
try
{
if (Profile.AlertTypes.Count == 0)
return;
Profile.AlertTypes.Clear();
Profile.AlertTypes.NotifyClear();
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(ClearAlertTypes_Click)}\r\n{ex.Message}");
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_ClearAlertTypesErrorLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_ClearErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void DeleteAlertType_Click(object sender, RoutedEventArgs e)
{
if (MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_DeleteLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_DeleteTitle"), MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
return;
try
{
var alertType = ((AlertTypeValue)((Button)e.Source).DataContext);
var index = Profile.AlertTypes.IndexOf(alertType);
Profile.AlertTypes.Remove(alertType);
Profile.AlertTypes.NotifyRemove(alertType, index);
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(DeleteAlertType_Click)}\r\n{ex.Message}");
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_DeleteAlertTypeErrorLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_DeleteErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void AddProfileName_Click(object sender, RoutedEventArgs e)
{
try
{
var profileName = new ProfileNameValue();
Profile.ProfileNames.Add(profileName);
Profile.ProfileNames.NotifyAdd(profileName);
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(AddProfileName_Click)}\r\n{ex.Message}");
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_AddProfileNameErrorLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_AddErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ClearProfileNames_Click(object sender, RoutedEventArgs e)
{
if (MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_ClearLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_ClearTitle"), MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
return;
try
{
if (Profile.ProfileNames.Count == 0)
return;
Profile.ProfileNames.Clear();
Profile.ProfileNames.NotifyClear();
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(ClearProfileNames_Click)}\r\n{ex.Message}");
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_ClearProfileNamesErrorLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_ClearErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void DeleteProfileName_Click(object sender, RoutedEventArgs e)
{
if (MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_DeleteLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_DeleteTitle"), MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
return;
try
{
var profileName = ((ProfileNameValue)((Button)e.Source).DataContext);
var index = Profile.ProfileNames.IndexOf(profileName);
Profile.ProfileNames.Remove(profileName);
Profile.ProfileNames.NotifyRemove(profileName, index);
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(DeleteProfileName_Click)}\r\n{ex.Message}");
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_DeleteProfileNameErrorLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigProfileWindow_DeleteErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}

View file

@ -0,0 +1,137 @@
<Window x:Class="ServerManagerTool.Plugin.Discord.Windows.ConfigWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pc="clr-namespace:ServerManagerTool.Plugin.Common;assembly=ServerManager.Plugin.Common"
Title="{DynamicResource ConfigWindow_Title}"
Icon="/ServerManager.Plugin.Discord;component/Art/favicon.ico"
Width="640" Height="480" MinWidth="640" MinHeight="480" ResizeMode="CanResizeWithGrip" WindowStyle="ToolWindow" WindowStartupLocation="CenterOwner" ShowInTaskbar="False" Closing="ConfigWindow_Closing" Loaded="ConfigWindow_Loaded">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/ServerManager.Plugin.Discord;component/Globalization/en-US/en-US.xaml"/>
</ResourceDictionary.MergedDictionaries>
<SolidColorBrush x:Key="BeigeBorder" Color="#FFD8CCBC"/>
<LinearGradientBrush x:Key="BeigeGradient" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFECE1D4" Offset="1"/>
<GradientStop Color="#FFEAE8E6"/>
</LinearGradientBrush>
<Style x:Key="GroupBoxStyle" TargetType="GroupBox" BasedOn="{StaticResource {x:Type GroupBox}}">
<Setter Property="BorderBrush" Value="{StaticResource BeigeBorder}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="Transparent"/>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid Background="{StaticResource BeigeGradient}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding NewVersionAvailable}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock Text="{DynamicResource Global_NewVersionAvailableLabel}" TextWrapping="Wrap" VerticalAlignment="Center" FontWeight="Bold"/>
<Button Width="22" Height="22" Click="DownloadPlugin_Click" Margin="10,0,0,0" ToolTip="{DynamicResource Global_DownloadNewVersionTooltip}">
<Image Source="{pc:Icon Path=/ServerManager.Plugin.Discord;component/Art/Download.ico,Size=32}" />
</Button>
</StackPanel>
<GroupBox Grid.Row="1" HorizontalAlignment="Stretch" Style="{StaticResource GroupBoxStyle}">
<GroupBox.Header>
<StackPanel Orientation="Horizontal" Margin="0,5,0,5">
<Button Width="22" Height="22" Click="PatchNotes_Click" ToolTip="{DynamicResource ConfigWindow_PatchNotesTooltip}">
<Image Source="{pc:Icon Path=/ServerManager.Plugin.Discord;component/Art/ChangeNotes.ico,Size=32}" />
</Button>
<Button Width="22" Height="22" Click="AddConfigProfile_Click" Margin="20,0,0,0" ToolTip="{DynamicResource ConfigWindow_AddConfigProfileTooltip}">
<Image Source="{pc:Icon Path=/ServerManager.Plugin.Discord;component/Art/Add.ico,Size=32}" />
</Button>
<Button Width="22" Height="22" Click="ClearConfigProfiles_Click" Margin="10,0,0,0" ToolTip="{DynamicResource ConfigWindow_ClearConfigProfilesTooltip}">
<Image Source="{pc:Icon Path=/ServerManager.Plugin.Discord;component/Art/Delete.ico,Size=32}" />
</Button>
</StackPanel>
</GroupBox.Header>
<DataGrid ItemsSource="{Binding PluginConfig.ConfigProfiles}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" CanUserReorderColumns="False" CanUserSortColumns="true" SelectionMode="Single" CanUserResizeColumns="False" CanUserResizeRows="False" RowHeaderWidth="25">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="{x:Static SystemColors.HighlightColor}"/>
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}" Color="{x:Static SystemColors.HighlightTextColor}"/>
</Style.Resources>
</Style>
</DataGrid.Resources>
<DataGrid.HorizontalGridLinesBrush>
<SolidColorBrush Color="#FFB4B4B4"/>
</DataGrid.HorizontalGridLinesBrush>
<DataGrid.VerticalGridLinesBrush>
<SolidColorBrush Color="#FFB4B4B4"/>
</DataGrid.VerticalGridLinesBrush>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
<Setter Property="Background" Value="Beige" />
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Width="*" Binding="{Binding Name}" IsReadOnly="True">
<DataGridTextColumn.Header>
<TextBlock Text="{DynamicResource ConfigWindow_NameColumnLabel}"/>
</DataGridTextColumn.Header>
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTemplateColumn Width="30" CanUserReorder="False" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Width="22" Height="22" Margin="0" IsTabStop="False" HorizontalAlignment="Center" VerticalAlignment="Center" Click="EditConfigProfile_Click" ToolTip="{DynamicResource ConfigWindow_EditConfigProfileTooltip}">
<Image Source="{pc:Icon Path=/ServerManager.Plugin.Discord;component/Art/Edit.ico,Size=32}" />
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="30" CanUserReorder="False" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Width="22" Height="22" Margin="0" IsTabStop="False" HorizontalAlignment="Center" VerticalAlignment="Center" Click="DeleteConfigProfile_Click" ToolTip="{DynamicResource ConfigWindow_DeleteConfigProfileTooltip}">
<Image Source="{pc:Icon Path=/ServerManager.Plugin.Discord;component/Art/Delete.ico,Size=32}" />
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</GroupBox>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,10,0">
<Button Content="{DynamicResource Global_SaveButtonLabel}" Margin="5" MinWidth="75" HorizontalAlignment="Right" Click="Save_Click"/>
<Button Content="{DynamicResource Global_CloseButtonLabel}" Margin="5" MinWidth="75" HorizontalAlignment="Left" IsCancel="True"/>
</StackPanel>
</Grid>
</Window>

View file

@ -0,0 +1,271 @@
using ServerManagerTool.Plugin.Common;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace ServerManagerTool.Plugin.Discord.Windows
{
/// <summary>
/// Interaction logic for ConfigWindow.xaml
/// </summary>
public partial class ConfigWindow : Window
{
private static readonly DependencyProperty PluginConfigProperty = DependencyProperty.Register(nameof(PluginConfig), typeof(DiscordPluginConfig), typeof(ConfigWindow));
public static readonly DependencyProperty LatestVersionProperty = DependencyProperty.Register(nameof(LatestVersion), typeof(Version), typeof(ConfigWindow), new PropertyMetadata(new Version()));
public static readonly DependencyProperty NewVersionAvailableProperty = DependencyProperty.Register(nameof(NewVersionAvailable), typeof(bool), typeof(ConfigWindow), new PropertyMetadata(false));
internal ConfigWindow(DiscordPlugin plugin, DiscordPluginConfig pluginConfig)
{
this.Plugin = plugin ?? new DiscordPlugin();
this.PluginConfig = pluginConfig ?? new DiscordPluginConfig();
InitializeComponent();
if (plugin.BetaEnabled)
Title = $"{Title} {ResourceUtils.GetResourceString(this.Resources, "Global_BetaModeLabel")}";
this.DataContext = this;
}
private DiscordPlugin Plugin
{
get;
set;
}
private DiscordPluginConfig PluginConfig
{
get { return GetValue(PluginConfigProperty) as DiscordPluginConfig; }
set { SetValue(PluginConfigProperty, value); }
}
public Version LatestVersion
{
get { return (Version)GetValue(LatestVersionProperty); }
set { SetValue(LatestVersionProperty, value); }
}
public bool NewVersionAvailable
{
get { return (bool)GetValue(NewVersionAvailableProperty); }
set { SetValue(NewVersionAvailableProperty, value); }
}
private void ConfigWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (DialogResult.HasValue && DialogResult.Value)
return;
if (PluginConfig.HasAnyChanges)
{
if (MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_CloseLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_CloseTitle"), MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
e.Cancel = true;
}
}
private void ConfigWindow_Loaded(object sender, RoutedEventArgs e)
{
CheckLatestVersionAsync().DoNotWait();
}
private void DownloadPlugin_Click(object sender, RoutedEventArgs e)
{
try
{
DownloadLatestVersion();
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(DownloadPlugin_Click)}\r\n{ex.Message}");
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_DownloadErrorLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_DownloadErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void AddConfigProfile_Click(object sender, RoutedEventArgs e)
{
try
{
var profile = new ConfigProfile();
if (EditProfile(profile))
PluginConfig.ConfigProfiles.Add(profile);
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(AddConfigProfile_Click)}\r\n{ex.Message}");
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_AddErrorLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_AddErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ClearConfigProfiles_Click(object sender, RoutedEventArgs e)
{
if (MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_ClearLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_ClearTitle"), MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
return;
try
{
if (PluginConfig.ConfigProfiles.Count == 0)
return;
PluginConfig.ConfigProfiles.Clear();
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(ClearConfigProfiles_Click)}\r\n{ex.Message}");
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_ClearErrorLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_ClearErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void DeleteConfigProfile_Click(object sender, RoutedEventArgs e)
{
if (MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_DeleteLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_DeleteTitle"), MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
return;
try
{
var profile = ((ConfigProfile)((Button)e.Source).DataContext);
PluginConfig.ConfigProfiles.Remove(profile);
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(DeleteConfigProfile_Click)}\r\n{ex.Message}");
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_DeleteErrorLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_DeleteErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void EditConfigProfile_Click(object sender, RoutedEventArgs e)
{
try
{
var profile = ((ConfigProfile)((Button)e.Source).DataContext);
EditProfile(profile);
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(EditConfigProfile_Click)}\r\n{ex.Message}");
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_EditErrorLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_EditErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void PatchNotes_Click(object sender, RoutedEventArgs e)
{
var url = string.Empty;
if (Plugin.BetaEnabled)
url = Config.Default.VersionBetaFeedUrl;
else
url = Config.Default.VersionFeedUrl;
if (!string.IsNullOrWhiteSpace(url))
{
var window = new VersionFeedWindow(Plugin, url);
window.Owner = this;
window.ShowDialog();
this.BringIntoView();
}
}
private void Save_Click(object sender, RoutedEventArgs e)
{
try
{
BackupExistingConfig();
SaveConfig();
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(Save_Click)}\r\n{ex.Message}");
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_SaveErrorLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_SaveErrorTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void BackupExistingConfig()
{
var configFile = Path.Combine(PluginHelper.PluginFolder, Config.Default.ConfigFile);
if (!File.Exists(configFile))
return;
var backupFile = Path.ChangeExtension(configFile, "bak");
try
{
File.Copy(configFile, backupFile, true);
}
catch
{
// do nothing, just exit if cannot backup existing config file
throw;
}
}
private async Task CheckLatestVersionAsync()
{
try
{
var newVersion = await NetworkUtils.CheckLatestVersionAsync(Plugin.BetaEnabled);
this.LatestVersion = newVersion;
this.NewVersionAvailable = Plugin.PluginVersion < newVersion;
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(CheckLatestVersionAsync)}\r\n{ex.Message}");
}
}
private void DownloadLatestVersion()
{
var cursor = this.Cursor;
try
{
this.Cursor = Cursors.Wait;
Task.Delay(500).Wait();
var latestZip = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), Config.Default.PluginZipFilename);
var sourceUrl = string.Empty;
if (Plugin.BetaEnabled)
sourceUrl = Config.Default.LatestBetaDownloadUrl;
else
sourceUrl = Config.Default.LatestDownloadUrl;
NetworkUtils.DownloadLatestVersion(sourceUrl, latestZip);
MessageBox.Show(ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_DownloadSuccessLabel"), ResourceUtils.GetResourceString(this.Resources, "ConfigWindow_DownloadSuccessTitle"), MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
Debug.WriteLine($"ERROR: {nameof(DownloadLatestVersion)}\r\n{ex.Message}");
}
finally
{
this.Cursor = cursor;
}
}
private bool EditProfile(ConfigProfile profile)
{
if (profile == null)
return false;
var window = new ConfigProfileWindow(Plugin, profile);
window.Owner = this;
var dialogResult = window.ShowDialog();
this.BringIntoView();
return dialogResult.HasValue && dialogResult.Value;
}
private void SaveConfig()
{
var configFile = Path.Combine(PluginHelper.PluginFolder, Config.Default.ConfigFile);
JsonUtils.SerializeToFile(PluginConfig, configFile);
PluginConfig?.CommitChanges();
}
}
}

View file

@ -0,0 +1,60 @@
<Window x:Class="ServerManagerTool.Plugin.Discord.Windows.VersionFeedWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ServerManagerTool.Plugin.Discord"
xmlns:pc="clr-namespace:ServerManagerTool.Plugin.Common;assembly=ServerManager.Plugin.Common"
MinWidth="400" MinHeight="400" Width="640" Height="480" WindowStyle="ToolWindow" WindowStartupLocation="CenterOwner" ShowInTaskbar="False" ResizeMode="CanResizeWithGrip"
Loaded="Window_Loaded"
Icon="../Art/favicon.ico" Title="{DynamicResource VersionFeedWindow_Title}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/ServerManager.Plugin.Discord;component/Globalization/en-US/en-US.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style x:Key="Version" TargetType="Label">
<Setter Property="Content" Value="{Binding Title}"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="Foreground" Value="Black"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsCurrent}" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="#0066CC"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid Background="{DynamicResource GradientBackground}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Margin="5,5,0,0" Content="{DynamicResource VersionFeedWindow_VersionFilterLabel}"/>
<ComboBox Grid.Row="0" Grid.Column="1" Margin="5,5,5,0" ItemsSource="{Binding FeedEntries}" SelectedValue="{Binding SelectedFeedEntry}" ToolTip="{DynamicResource VersionFeedWindow_VersionFilterTooltip}">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}" >
<Setter Property="Height" Value="20" />
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:VersionFeedEntry}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<Label Padding="0,-1,0,-1" VerticalAlignment="Center" Style="{DynamicResource Version}"/>
<TextBlock Text="{Binding Updated, StringFormat= - {0:G}}" Margin="5,0,0,0" Padding="0,-1,0,-1" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<WebBrowser Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Margin="5" local:BrowserBehavior.Html="{Binding SelectedFeedEntry.Content}"/>
</Grid>
</Window>

View file

@ -0,0 +1,81 @@
using ServerManagerTool.Plugin.Common;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
namespace ServerManagerTool.Plugin.Discord.Windows
{
/// <summary>
/// Interaction logic for VersionFeedWindow.xaml
/// </summary>
public partial class VersionFeedWindow : Window
{
public static readonly DependencyProperty FeedEntriesProperty = DependencyProperty.Register(nameof(FeedEntries), typeof(ObservableCollection<VersionFeedEntry>), typeof(VersionFeedWindow), new PropertyMetadata(new ObservableCollection<VersionFeedEntry>()));
public static readonly DependencyProperty SelectedFeedEntryProperty = DependencyProperty.Register(nameof(SelectedFeedEntry), typeof(VersionFeedEntry), typeof(VersionFeedWindow), new PropertyMetadata(null));
private string feedUri = string.Empty;
public VersionFeedWindow(DiscordPlugin plugin, string feedUri)
{
this.Plugin = plugin ?? new DiscordPlugin();
this.feedUri = feedUri;
InitializeComponent();
this.DataContext = this;
}
private DiscordPlugin Plugin
{
get;
set;
}
public ObservableCollection<VersionFeedEntry> FeedEntries
{
get { return (ObservableCollection<VersionFeedEntry>)GetValue(FeedEntriesProperty); }
set { SetValue(FeedEntriesProperty, value); }
}
public VersionFeedEntry SelectedFeedEntry
{
get { return (VersionFeedEntry)GetValue(SelectedFeedEntryProperty); }
set { SetValue(SelectedFeedEntryProperty, value); }
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
LoadFeed();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, ResourceUtils.GetResourceString(this.Resources, "VersionFeedWindow_Load_FailedTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void LoadFeed()
{
FeedEntries.Clear();
if (string.IsNullOrWhiteSpace(this.feedUri))
return;
var versionFeed = VersionFeedUtils.LoadVersionFeed(this.feedUri, Plugin.PluginVersion.ToString());
if (versionFeed == null)
return;
foreach (var entry in versionFeed.Entries)
{
if (entry == null)
continue;
FeedEntries.Add(entry);
}
SelectedFeedEntry = FeedEntries.OrderByDescending(e => e.Updated).FirstOrDefault();
}
}
}