Compare commits

...
8 Commits
Author SHA1 Message Date
amacocianandGitHub 6a6eea5d02 Implement search bar for builds and skills (#50)
* Add search bars to builds and skills

* Fix search bar not appearing.
Improve search function.

* Increment version

* Rename version check so it can be added to status checks
2022-08-18 21:26:57 +02:00
amacocianandGitHub 8fe9eeb32f Icon downloader bugfixes (#47)
* Fix icon download to properly finish
2022-08-16 19:47:25 +02:00
amacocianandGitHub 4c1025243b Background Icon Downloader (#44)
* Download icons in the background

* Fix icon downloader browser setup
2022-08-16 17:51:35 +02:00
amacocianandGitHub f24eb02ca1 Pipeline to check that version is updated (#43)
* Pipeline to check that version is updated
2022-08-13 00:24:54 +02:00
amacocianandGitHub b01c09491f Download and check icons at startup (#41) 2022-08-12 23:41:22 +02:00
amacocianandGitHub 54ae742899 Increment version (#40) 2022-08-12 13:22:59 +02:00
amacocianandGitHub 4c862463d7 Fix IconRetriever (#38)
Use browser to bypass header issues with http client
2022-08-12 13:15:46 +02:00
amacocianandGitHub b455c89ddc Minor improvements (#36)
Deprecate Microsoft.Xaml.Behaviors.Wpf
Reverse version order in management view
2022-08-04 23:05:55 +02:00
54 changed files with 1244 additions and 308 deletions
+45
View File
@@ -0,0 +1,45 @@
name: Daybreak Version Check
on:
pull_request:
branches:
- master
jobs:
check_version:
strategy:
matrix:
targetplatform: [x64]
runs-on: windows-latest
env:
Configuration: Release
Solution_Path: Daybreak.sln
Actions_Allow_Unsecure_Commands: true
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Get Latest Tag
id: getLatestTag
uses: WyriHaximus/github-action-get-previous-tag@v1
- name: Build Daybreak project
run: dotnet build Daybreak -c $env:Configuration
- name: Set version variable
run: |
$version = .\Scripts\GetBuildVersion.ps1
echo "::set-env name=Version::$version"
- name: Check version difference
run: |
.\Scripts\CompareVersions -currentVersion ${{ env.Version }} -lastVersion ${{ env.LatestReleaseTag }}
env:
LatestReleaseTag: ${{ steps.getLatestTag.outputs.tag }}
+9 -1
View File
@@ -11,9 +11,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pipelines", "Pipelines", "{
ProjectSection(SolutionItems) = preProject
.github\workflows\cd.yaml = .github\workflows\cd.yaml
.github\workflows\ci.yaml = .github\workflows\ci.yaml
.github\workflows\version_check.yaml = .github\workflows\version_check.yaml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Daybreak.Installer", "Daybreak.Installer\Daybreak.Installer.csproj", "{4E2BB805-135D-4F02-8C53-3D8B6876D323}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Daybreak.Installer", "Daybreak.Installer\Daybreak.Installer.csproj", "{4E2BB805-135D-4F02-8C53-3D8B6876D323}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{41AE8C5D-25E1-4B08-8D65-868552421A63}"
ProjectSection(SolutionItems) = preProject
Scripts\BuildRelease.ps1 = Scripts\BuildRelease.ps1
Scripts\GetBuildVersion.ps1 = Scripts\GetBuildVersion.ps1
Scripts\CompareVersions.ps1 = Scripts\CompareVersions.ps1
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
+2 -2
View File
@@ -1,9 +1,9 @@
using System;
using Microsoft.Xaml.Behaviors;
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace Daybreak.Behaviors
+2 -2
View File
@@ -1,7 +1,7 @@
using System;
using Microsoft.Xaml.Behaviors;
using System;
using System.Extensions;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace Daybreak.Behaviors
{
@@ -37,7 +37,5 @@ namespace Daybreak.Configuration
public bool PlaceShortcut { get; set; }
[JsonProperty("AutoCheckUpdate")]
public bool AutoCheckUpdate { get; set; } = true;
[JsonProperty("KeepLocalIconCache")]
public bool KeepLocalIconCache { get; set; } = true;
}
}
@@ -16,6 +16,8 @@ namespace Daybreak.Configuration
public bool LaunchGuildwarsAsCurrentUser { get; set; } = true;
[JsonProperty("CanInterceptKeys")]
public bool CanInterceptKeys { get; set; }
[JsonProperty("DownloadIcons")]
public bool DownloadIcons { get; set; }
[JsonProperty("Macros")]
public List<KeyMacro> Macros { get; set; } = new();
}
+18 -3
View File
@@ -22,6 +22,7 @@ using LiteDB;
using Daybreak.Services.Options;
using Daybreak.Models;
using Microsoft.CorrelationVector;
using System.Logging;
namespace Daybreak.Configuration
{
@@ -45,22 +46,35 @@ namespace Daybreak.Configuration
{
serviceProducer.ThrowIfNull(nameof(serviceProducer));
serviceProducer.RegisterSingleton<ILogsManager, JsonLogsManager>();
serviceProducer.RegisterSingleton<IDebugLogsWriter, Services.Logging.DebugLogsWriter>();
serviceProducer.RegisterSingleton<ILoggerFactory, LoggerFactory>(sp =>
{
var factory = new LoggerFactory();
factory.AddProvider(new CVLoggerProvider(sp.GetService<ILogsWriter>()));
return factory;
});
serviceProducer.RegisterSingleton<ILogsWriter, CompositeLogsWriter>(sp => new CompositeLogsWriter(
sp.GetService<ILogsManager>(),
sp.GetService<IDebugLogsWriter>()));
serviceProducer.RegisterScoped((sp) => new ScopeMetadata(new CorrelationVector()));
serviceProducer.RegisterSingleton<ViewManager>(registerAllInterfaces: true);
serviceProducer.RegisterSingleton<IConfigurationManager, ConfigurationManager>();
serviceProducer.RegisterSingleton<ILiteDatabase, LiteDatabase>(sp => new LiteDatabase("Daybreak.db"));
serviceProducer.RegisterSingleton<IMutexHandler, MutexHandler>();
serviceProducer.RegisterSingleton<IShortcutManager, ShortcutManager>();
serviceProducer.RegisterSingleton<IIconBrowser, IconBrowser>();
serviceProducer.RegisterSingleton<IIconDownloader, IconDownloader>();
serviceProducer.RegisterScoped<ICredentialManager, CredentialManager>();
serviceProducer.RegisterScoped<IApplicationLauncher, ApplicationLauncher>();
serviceProducer.RegisterScoped<IScreenshotProvider, ScreenshotProvider>();
serviceProducer.RegisterScoped<IBloogumClient, BloogumClient>();
serviceProducer.RegisterScoped<IApplicationUpdater, ApplicationUpdater>();
serviceProducer.RegisterScoped<IBuildTemplateManager, BuildTemplateManager>();
serviceProducer.RegisterScoped<IIconRetriever, IconRetriever>();
serviceProducer.RegisterScoped<IIconCache, IconCache>();
serviceProducer.RegisterScoped<IPrivilegeManager, PrivilegeManager>();
serviceProducer.RegisterScoped<IScreenManager, ScreenManager>();
serviceProducer.RegisterLogWriter<ILogsManager, JsonLogsManager>();
serviceProducer.RegisterScoped((sp) => new ScopeMetadata(new CorrelationVector()));
}
public static void RegisterViews(IViewProducer viewProducer)
@@ -81,6 +95,7 @@ namespace Daybreak.Configuration
viewProducer.RegisterView<ScreenChoiceView>();
viewProducer.RegisterView<VersionManagementView>();
viewProducer.RegisterView<LogsView>();
viewProducer.RegisterView<IconDownloadView>();
}
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:behaviors="clr-namespace:Daybreak.Behaviors"
xmlns:converters="clr-namespace:Daybreak.Converters"
x:Name="_this"
@@ -28,6 +28,8 @@ namespace Daybreak.Controls
private const string BrowserDownloadLink = "https://developer.microsoft.com/en-us/microsoft-edge/webview2/";
private static CoreWebView2Environment coreWebView2Environment;
public event EventHandler<string> FavoriteUriChanged;
public event EventHandler MaximizeClicked;
public event EventHandler<Build> BuildDecoded;
@@ -35,16 +37,15 @@ namespace Daybreak.Controls
private ILiveOptions<ApplicationConfiguration> liveOptions;
private ILogger<ChromiumBrowserWrapper> logger;
private IBuildTemplateManager buildTemplateManager;
private CoreWebView2Environment coreWebView2Environment;
[GenerateDependencyProperty(InitialValue = true)]
private bool canDownloadBuild;
[GenerateDependencyProperty(InitialValue = true)]
private bool canNavigate;
[GenerateDependencyProperty(InitialValue = true)]
private bool controlsEnabled;
[GenerateDependencyProperty]
private bool browserSupported;
[GenerateDependencyProperty(InitialValue = null)]
private bool? browserSupported;
[GenerateDependencyProperty]
private bool addressBarReadonly;
[GenerateDependencyProperty]
@@ -80,7 +81,7 @@ namespace Daybreak.Controls
}
}
public async void InitializeBrowser(
public async Task InitializeBrowser(
ILiveOptions<ApplicationConfiguration> liveOptions,
IBuildTemplateManager buildTemplateManager,
ILogger<ChromiumBrowserWrapper> logger)
@@ -107,7 +108,11 @@ namespace Daybreak.Controls
try
{
this.coreWebView2Environment = System.Extensions.TaskExtensions.RunSync(() => CoreWebView2Environment.CreateAsync(null, "BrowserData", null));
if (coreWebView2Environment is null)
{
coreWebView2Environment = System.Extensions.TaskExtensions.RunSync(() => CoreWebView2Environment.CreateAsync(null, "BrowserData", null));
}
this.BrowserSupported = true;
}
catch(Exception e)
@@ -122,7 +127,8 @@ namespace Daybreak.Controls
if (this.BrowserSupported is true)
{
this.WebBrowser.IsEnabled = true;
await this.WebBrowser.EnsureCoreWebView2Async(this.coreWebView2Environment);
this.BrowserEnabled = true;
await this.WebBrowser.EnsureCoreWebView2Async(coreWebView2Environment);
this.AddressBarReadonly = this.liveOptions.Value.AddressBarReadonly;
this.CanDownloadBuild = this.liveOptions.Value.ExperimentalFeatures.DynamicBuildLoading;
this.WebBrowser.CoreWebView2.NewWindowRequested += (browser, args) => args.Handled = true;
@@ -207,6 +213,11 @@ namespace Daybreak.Controls
return;
}
if (this.buildTemplateManager is null)
{
return;
}
if (this.buildTemplateManager.IsTemplate(maybeTemplate) is false)
{
return;
+35
View File
@@ -0,0 +1,35 @@
<UserControl x:Class="Daybreak.Controls.SearchTextBox"
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:local="clr-namespace:Daybreak.Controls"
x:Name="_this"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></BooleanToVisibilityConverter>
</UserControl.Resources>
<Grid>
<TextBox Foreground="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=Foreground, Mode=OneWay}"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontSize, Mode=OneWay}"
FontFamily="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontFamily, Mode=OneWay}"
FontStretch="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontStretch, Mode=OneWay}"
FontWeight="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontWeight, Mode=OneWay}"
FontStyle="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontStyle, Mode=OneWay}"
TextChanged="TextBox_TextChanged"
Background="Transparent"/>
<TextBlock Margin="5, 0, 0, 0"
Text="Search"
Background="Transparent"
Opacity="0.5"
IsHitTestVisible="False"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontSize, Mode=OneWay}"
FontFamily="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontFamily, Mode=OneWay}"
FontStretch="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontStretch, Mode=OneWay}"
FontWeight="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontWeight, Mode=OneWay}"
FontStyle="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=FontStyle, Mode=OneWay}"
Foreground="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=Foreground, Mode=OneWay}"
Visibility="{Binding RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}, Path=PlaceholderVisibility, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Grid>
</UserControl>
+42
View File
@@ -0,0 +1,42 @@
using System;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for SearchTextBox.xaml
/// </summary>
public partial class SearchTextBox : UserControl
{
public event EventHandler<string> TextChanged;
[GenerateDependencyProperty(InitialValue = true)]
private bool placeholderVisibility;
[GenerateDependencyProperty]
private string searchText;
public SearchTextBox()
{
this.InitializeComponent();
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var searchText = e.Source.As<TextBox>().Text;
if (searchText.IsNullOrWhiteSpace())
{
this.PlaceholderVisibility = true;
}
else
{
this.PlaceholderVisibility = false;
}
this.SearchText = searchText;
this.TextChanged?.Invoke(this, searchText);
}
}
}
+26 -9
View File
@@ -4,11 +4,12 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
xmlns:interactivity="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:interactivity="http://schemas.microsoft.com/xaml/behaviors"
xmlns:behaviors="clr-namespace:Daybreak.Behaviors"
xmlns:converters="clr-namespace:Daybreak.Converters"
mc:Ignorable="d"
x:Name="_this"
Unloaded="BuildTemplate_Unloaded"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="Transparent" MouseLeftButtonDown="Grid_MouseLeftButtonDown">
<Grid.RowDefinitions>
@@ -156,14 +157,30 @@
<local:ChromiumBrowserWrapper x:Name="SkillBrowser" ControlsEnabled="False" Width="0" AddressBarReadonly="True" CanNavigate="True"></local:ChromiumBrowserWrapper>
</Grid>
<Grid Grid.Column="1" Grid.RowSpan="6">
<ListView x:Name="SkillsListView" Background="Transparent" Width="0"
ItemsSource="{Binding ElementName=_this, Path=AvailableSkills, Mode=OneWay}" MouseDoubleClick="ListView_MouseDoubleClick">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock FontSize="16" Foreground="White" TextWrapping="Wrap" Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid x:Name="SkillListContainer">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<local:SearchTextBox FontSize="20"
Foreground="White"
Background="Transparent"
SearchText="{Binding ElementName=_this, Path=SkillSearchText, Mode=TwoWay}"
TextChanged="SearchTextBox_TextChanged"></local:SearchTextBox>
<ScrollViewer VerticalScrollBarVisibility="Hidden"
HorizontalScrollBarVisibility="Disabled"
Grid.Row="1"
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
<ListView x:Name="SkillsListView" Background="Transparent"
ItemsSource="{Binding ElementName=_this, Path=AvailableSkills, Mode=OneWay}" MouseDoubleClick="ListView_MouseDoubleClick">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock FontSize="16" Foreground="White" TextWrapping="Wrap" Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
</Grid>
</Grid>
</Grid>
</UserControl>
@@ -7,11 +7,14 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Core.Extensions;
using System.Extensions;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
using Utils;
namespace Daybreak.Controls
{
@@ -27,11 +30,15 @@ namespace Daybreak.Controls
private bool suppressBuildChanged = false;
private bool loadedProperties = false;
private IIconBrowser iconBrowser;
private BuildEntry loadedBuild;
private SkillTemplate selectingSkillTemplate;
private CancellationTokenSource cancellationTokenSource = new();
public event EventHandler BuildChanged;
[GenerateDependencyProperty]
private string skillSearchText;
[GenerateDependencyProperty]
private Profession primaryProfession;
[GenerateDependencyProperty]
@@ -63,13 +70,15 @@ namespace Daybreak.Controls
this.DataContextChanged += this.BuildTemplate_DataContextChanged;
}
public void InitializeTemplate(
IIconRetriever iconRetriever,
public async void InitializeTemplate(
IIconCache iconRetriever,
IIconBrowser iconBrowser,
ILiveOptions<ApplicationConfiguration> liveOptions,
IBuildTemplateManager buildTemplateManager,
ILogger<ChromiumBrowserWrapper> logger)
{
this.SkillBrowser.InitializeBrowser(liveOptions, buildTemplateManager, logger);
this.iconBrowser = iconBrowser.ThrowIfNull();
await this.SkillBrowser.InitializeBrowser(liveOptions, buildTemplateManager, logger);
this.SkillTemplate0.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate1.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate2.InitializeSkillTemplate(iconRetriever);
@@ -78,6 +87,9 @@ namespace Daybreak.Controls
this.SkillTemplate5.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate6.InitializeSkillTemplate(iconRetriever);
this.SkillTemplate7.InitializeSkillTemplate(iconRetriever);
this.HideSkillListView();
this.HideInfoBrowser();
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
@@ -131,6 +143,11 @@ namespace Daybreak.Controls
}
}
private void BuildTemplate_Unloaded(object sender, RoutedEventArgs e)
{
this.cancellationTokenSource.Cancel();
}
private void InitializeProperties()
{
this.PrimaryProfession = Profession.None;
@@ -196,6 +213,9 @@ namespace Daybreak.Controls
var possibleSkills = Skill.Skills
.Where(s => s.Profession == this.PrimaryProfession || s.Profession == this.SecondaryProfession || s.Profession == Profession.None)
.Where(s => s != Skill.NoSkill)
.Where(s => this.SkillSearchText.IsNullOrWhiteSpace() ?
true :
StringUtils.MatchesSearchString(s.Name.Replace("\"", "").Replace("!", ""), this.SkillSearchText.Replace("\"", "").Replace("!", "")))
.OrderBy(s => s.Name);
this.AvailableSkills.ClearAnd().AddRange(possibleSkills);
@@ -286,7 +306,7 @@ namespace Daybreak.Controls
if (this.SkillBrowser.BrowserSupported is true)
{
this.SkillBrowser.Width = 400;
this.SkillsListView.Width = 0;
this.SkillListContainer.Width = 0;
}
}
@@ -301,12 +321,12 @@ namespace Daybreak.Controls
private void ShowSkillListView()
{
this.SkillBrowser.Width = 0;
this.SkillsListView.Width = 400;
this.SkillListContainer.Width = 400;
}
private void HideSkillListView()
{
this.SkillsListView.Width = 0;
this.SkillListContainer.Width = 0;
}
private void HelpButtonPrimary_Clicked(object sender, System.EventArgs e)
@@ -352,6 +372,7 @@ namespace Daybreak.Controls
var skill = sender.As<SkillTemplate>().DataContext.As<Skill>();
if (skill == Skill.NoSkill)
{
this.SkillSearchText = string.Empty;
this.ShowSkillListView();
this.selectingSkillTemplate = sender.As<SkillTemplate>();
}
@@ -363,6 +384,11 @@ namespace Daybreak.Controls
e.Handled = true;
}
private void SearchTextBox_TextChanged(object sender, string e)
{
this.LoadSkills();
}
private void SkillTemplate_RemoveClicked(object sender, System.EventArgs e)
{
sender.As<SkillTemplate>().DataContext = Skill.NoSkill;
@@ -404,5 +430,16 @@ namespace Daybreak.Controls
sender.As<ListView>().Items.Count;
}
}
private void ScrollViewer_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
if (sender is not ScrollViewer scrollViewer)
{
return;
}
e.Handled = true;
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);
}
}
}
@@ -1,8 +1,9 @@
<UserControl x:Class="Daybreak.Controls.SkillTemplate"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:webview="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
@@ -1,10 +1,6 @@
using Daybreak.Launch;
using Daybreak.Models.Builds;
using Daybreak.Models.Builds;
using Daybreak.Services.IconRetrieve;
using System;
using System.Extensions;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
@@ -23,7 +19,7 @@ namespace Daybreak.Controls
public event EventHandler<RoutedEventArgs> Clicked;
public event EventHandler RemoveClicked;
private IIconRetriever iconRetriever;
private IIconCache iconRetriever;
[GenerateDependencyProperty]
private ImageSource imageSource;
@@ -36,24 +32,28 @@ namespace Daybreak.Controls
this.DataContextChanged += SkillTemplate_DataContextChanged;
}
public void InitializeSkillTemplate(IIconRetriever iconRetriever)
public void InitializeSkillTemplate(IIconCache iconRetriever)
{
this.iconRetriever = iconRetriever;
this.SkillTemplate_DataContextChanged(this, new DependencyPropertyChangedEventArgs(UserControl.DataContextProperty, null, this.DataContext));
}
private void SkillTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
private async void SkillTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (this.iconRetriever is null)
{
return;
}
if (e.NewValue is Skill skill)
{
if (skill != Skill.NoSkill)
{
Task.Run(() => this.GetImageStream(skill)).ContinueWith((previousTask) =>
var maybeUri = await this.iconRetriever.GetIconUri(skill).ConfigureAwait(true);
if (maybeUri.ExtractValue() is Uri uri)
{
this.Dispatcher.Invoke(() =>
{
this.ImageSource = this.GetImageSource(previousTask.Result);
});
});
this.ImageSource = new BitmapImage(uri);
}
}
else if (this.ImageSource is not null)
{
@@ -90,32 +90,5 @@ namespace Daybreak.Controls
return false;
}
private async Task<Stream> GetImageStream(Skill skill)
{
if (this.iconRetriever is null)
{
return null;
}
var maybeStream = await this.iconRetriever.GetIcon(skill);
return maybeStream.ExtractValue();
}
private ImageSource GetImageSource(Stream stream)
{
if (stream is null)
{
return null;
}
return this.Dispatcher.Invoke(() =>
{
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = stream;
bitmapImage.CacheOption = BitmapCacheOption.OnDemand;
bitmapImage.EndInit();
return bitmapImage;
});
}
}
}
@@ -1,4 +1,5 @@
using System;
using System.Extensions;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
@@ -19,7 +20,7 @@ namespace Daybreak.Converters
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return GetVisibility(value);
return this.GetVisibility(value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
@@ -29,17 +30,24 @@ namespace Daybreak.Converters
private object GetVisibility(object value)
{
if (!(value is bool))
return DependencyProperty.UnsetValue;
bool objValue = (bool)value;
if (value is not bool)
{
return this.IsHidden ?
Visibility.Hidden :
Visibility.Collapsed;
}
var objValue = value.Cast<bool>();
if ((objValue && TriggerValue && IsHidden) || (!objValue && !TriggerValue && IsHidden))
{
return Visibility.Hidden;
}
if ((objValue && TriggerValue && !IsHidden) || (!objValue && !TriggerValue && !IsHidden))
{
return Visibility.Collapsed;
}
return Visibility.Visible;
}
}
+10 -3
View File
@@ -10,21 +10,21 @@
<LangVersion>preview</LangVersion>
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
<Version>0.9.3.1</Version>
<Version>0.9.4</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.11.43" />
<PackageReference Include="LiteDB" Version="5.0.12" />
<PackageReference Include="Microsoft.CorrelationVector" Version="1.0.42" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1264.42" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NReco.Logging.File" Version="1.1.5" />
<PackageReference Include="securifybv.ShellLink" Version="0.1.0" />
<PackageReference Include="Slim" Version="1.7.3" />
<PackageReference Include="System.Windows.Interactivity.WPF" Version="2.0.20525" />
<PackageReference Include="SystemExtensions.NetCore" Version="1.0.1" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.5.0" />
<PackageReference Include="WCL" Version="1.0.2" />
<PackageReference Include="WpfExtended" Version="0.6.2" />
@@ -45,6 +45,9 @@
<Compile Update="Controls\Buttons\CancelButton.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Views\IconDownloadView.xaml.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
@@ -68,6 +71,10 @@
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
<Page Update="Views\IconDownloadView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(Configuration)' == 'Release'">
+7 -1
View File
@@ -2,7 +2,8 @@
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:webview="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
xmlns:local="clr-namespace:Daybreak.Launch"
xmlns:wcl="clr-namespace:WCL;assembly=WCL"
mc:Ignorable="d"
@@ -102,5 +103,10 @@
<Grid x:Name="Container" Grid.Row="1">
</Grid>
<wcl:Border OnResize="Border_OnResize" Grid.RowSpan="2" Active="True"></wcl:Border>
<webview:WebView2 x:Name="BackgroundWebView"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Visibility="Collapsed"
Grid.Row="1"></webview:WebView2>
</Grid>
</Window>
+17 -6
View File
@@ -1,5 +1,6 @@
using Daybreak.Configuration;
using Daybreak.Services.Bloogum;
using Daybreak.Services.IconRetrieve;
using Daybreak.Services.Privilege;
using Daybreak.Services.Screenshots;
using Daybreak.Services.Updater;
@@ -8,6 +9,7 @@ using Daybreak.Views;
using Pepa.Wpf.Utilities;
using System;
using System.Configuration;
using System.Core.Extensions;
using System.Diagnostics;
using System.Extensions;
using System.Threading;
@@ -32,6 +34,7 @@ namespace Daybreak.Launch
private readonly IBloogumClient bloogumClient;
private readonly IApplicationUpdater applicationUpdater;
private readonly IPrivilegeManager privilegeManager;
private readonly IIconDownloader iconDownloader;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
private readonly CancellationTokenSource cancellationToken = new();
@@ -48,14 +51,16 @@ namespace Daybreak.Launch
IBloogumClient bloogumClient,
IApplicationUpdater applicationUpdater,
IPrivilegeManager privilegeManager,
IIconDownloader iconDownloader,
ILiveOptions<ApplicationConfiguration> liveOptions)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.screenshotProvider = screenshotProvider.ThrowIfNull(nameof(screenshotProvider));
this.bloogumClient = bloogumClient.ThrowIfNull(nameof(bloogumClient));
this.applicationUpdater = applicationUpdater.ThrowIfNull(nameof(applicationUpdater));
this.privilegeManager = privilegeManager.ThrowIfNull(nameof(privilegeManager));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.viewManager = viewManager.ThrowIfNull();
this.screenshotProvider = screenshotProvider.ThrowIfNull();
this.bloogumClient = bloogumClient.ThrowIfNull();
this.applicationUpdater = applicationUpdater.ThrowIfNull();
this.privilegeManager = privilegeManager.ThrowIfNull();
this.iconDownloader = iconDownloader.ThrowIfNull();
this.liveOptions = liveOptions.ThrowIfNull();
this.InitializeComponent();
this.CurrentVersionText = this.applicationUpdater.CurrentVersion.ToString();
this.IsRunningAsAdmin = this.privilegeManager.AdminPrivileges;
@@ -66,6 +71,7 @@ namespace Daybreak.Launch
{
this.SetupImageCycle();
this.CheckForUpdates();
this.SetupBackgroundBrowser();
}
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
@@ -101,6 +107,11 @@ namespace Daybreak.Launch
NativeMethods.SendMessage(new WindowInteropHelper(this).Handle, NativeMethods.WM_SYSCOMMAND, (IntPtr)e, IntPtr.Zero);
}
private void SetupBackgroundBrowser()
{
this.iconDownloader.SetBrowser(this.BackgroundWebView);
}
private void SetupImageCycle()
{
TaskExtensions.RunPeriodicAsync(() => this.Dispatcher.Invoke(() => this.UpdateRandomImage()), TimeSpan.Zero, TimeSpan.FromSeconds(15), this.cancellationToken.Token);
@@ -8,6 +8,7 @@ namespace Daybreak.Models.Browser
[JsonConverter(typeof(StringEnumConverter))]
public enum PayloadKeys
{
None,
ContextMenu
}
+6 -5
View File
@@ -143,7 +143,7 @@ namespace Daybreak.Models.Builds
public static Skill Flurry { get; } = new() { Id = 344, Name = "Flurry", Profession = Profession.Warrior };
public static Skill Frenzy { get; } = new() { Id = 346, Name = "Frenzy", Profession = Profession.Warrior };
public static Skill Coward { get; } = new() { Id = 869, Name = "\"Coward!\"", Profession = Profession.Warrior };
public static Skill OnYourKnees { get; } = new() { Id = 906, Name = "On Your Knees!", Profession = Profession.Warrior };
public static Skill OnYourKnees { get; } = new() { Id = 906, Name = "\"On Your Knees!\"", Profession = Profession.Warrior };
public static Skill YoureAllAlone { get; } = new() { Id = 1412, Name = "\"You're All Alone!\"", Profession = Profession.Warrior };
public static Skill FrenziedDefense { get; } = new() { Id = 1700, Name = "Frenzied Defense", Profession = Profession.Warrior };
public static Skill Grapple { get; } = new() { Id = 2011, Name = "Grapple", Profession = Profession.Warrior };
@@ -702,7 +702,7 @@ namespace Daybreak.Models.Builds
public static Skill Echo { get; } = new() { Id = 74, Name = "Echo", Profession = Profession.Mesmer };
public static Skill ArcaneEcho { get; } = new() { Id = 75, Name = "Arcane Echo", Profession = Profession.Mesmer };
public static Skill Epidemic { get; } = new() { Id = 78, Name = "Epidemic", Profession = Profession.Mesmer };
public static Skill LyssasBalance { get; } = new() { Id = 877, Name = "Lyssa's Balance, Profession = Profession.Mesmer" };
public static Skill LyssasBalance { get; } = new() { Id = 877, Name = "Lyssa's Balance", Profession = Profession.Mesmer };
public static Skill SignetofDisenchantment { get; } = new() { Id = 882, Name = "Signet of Disenchantment", Profession = Profession.Mesmer };
public static Skill ShatterStorm { get; } = new() { Id = 933, Name = "Shatter Storm", Profession = Profession.Mesmer };
public static Skill ExpelHexes { get; } = new() { Id = 954, Name = "Expel Hexes", Profession = Profession.Mesmer };
@@ -1053,7 +1053,7 @@ namespace Daybreak.Models.Builds
public static Skill FeastofSouls { get; } = new() { Id = 980, Name = "Feast of Souls", Profession = Profession.Ritualist };
public static Skill RitualLord { get; } = new() { Id = 1217, Name = "Ritual Lord", Profession = Profession.Ritualist };
public static Skill AttunedWasSongkai { get; } = new() { Id = 1220, Name = "Attuned Was Songkai", Profession = Profession.Ritualist };
public static Skill AnguishedWasLingwah { get; } = new() { Id = 1223, Name = "Anguished Was Lingwah, Profession = Profession.Ritualist" };
public static Skill AnguishedWasLingwah { get; } = new() { Id = 1223, Name = "Anguished Was Lingwah", Profession = Profession.Ritualist };
public static Skill ExplosiveGrowth { get; } = new() { Id = 1229, Name = "Explosive Growth", Profession = Profession.Ritualist };
public static Skill BoonofCreation { get; } = new() { Id = 1230, Name = "Boon of Creation", Profession = Profession.Ritualist };
public static Skill SpiritChanneling { get; } = new() { Id = 1231, Name = "Spirit Channeling", Profession = Profession.Ritualist };
@@ -1483,8 +1483,8 @@ namespace Daybreak.Models.Builds
public static Skill VolfenBlessing { get; } = new() { Id = 2379, Name = "Volfen Blessing", Profession = Profession.None };
public static Skill TimeWard { get; } = new() { Id = 3422, Name = "Time Ward", Profession = Profession.Mesmer };
public static Skill SoulTaker { get; } = new() { Id = 3423, Name = "Soul Taker", Profession = Profession.Necromancer };
public static Skill OverTheLimit { get; } = new() { Id = 3424, Name = "Over The Limit", Profession = Profession.Elementalist };
public static Skill JudgementStrike { get; } = new() { Id = 3425, Name = "Judgement Strike", Profession = Profession.Monk };
public static Skill OverTheLimit { get; } = new() { Id = 3424, Name = "Over The Limit", AlternativeName = "Over the Limit", Profession = Profession.Elementalist };
public static Skill JudgementStrike { get; } = new() { Id = 3425, Name = "Judgement Strike", AlternativeName = "Judgment Strike", Profession = Profession.Monk };
public static Skill SevenWeaponsStance { get; } = new() { Id = 3426, Name = "Seven Weapons Stance", Profession = Profession.Warrior };
public static Skill Togetherasone { get; } = new() { Id = 3427, Name = "\"Together as one!\"", Profession = Profession.Ranger };
public static Skill ShadowTheft { get; } = new() { Id = 3428, Name = "Shadow Theft", Profession = Profession.Assassin };
@@ -3022,6 +3022,7 @@ namespace Daybreak.Models.Builds
public Profession Profession { get; private set; }
public string Name { get; private set; }
public int Id { get; private set; }
public string AlternativeName { get; private set; }
private Skill()
{
}
+8
View File
@@ -0,0 +1,8 @@
namespace Daybreak.Models
{
public sealed class IconPayload
{
public string SkillUrl { get; set; }
public string SkillImage { get; set; }
}
}
+11
View File
@@ -0,0 +1,11 @@
using Daybreak.Models.Builds;
namespace Daybreak.Models
{
public sealed class IconRequest
{
public Skill Skill { get; set; }
public string IconBase64 { get; set; }
public bool Finished { get; set; }
}
}
@@ -0,0 +1,78 @@
using System.ComponentModel;
namespace Daybreak.Models.Progress
{
public sealed class IconDownloadStatus : INotifyPropertyChanged
{
public static readonly IconDownloadStep StartingStep = new StartingIconDownloadStep();
public static readonly IconDownloadStep Finished = new FinishedIconDownloadStep();
public static readonly IconDownloadStep BrowserNotSupported = new NotSupportedIconDownloadStep();
public static IconDownloadStep Checking(string iconName, double progress) => new CheckingIconDownloadStep(iconName, progress);
public static IconDownloadStep Downloading(string iconName, double progress) => new DownloadingIconDownloadStep(iconName, progress);
public static IconDownloadStep Stopped(double progress) => new StoppedIconDownloadStep(progress);
private IconDownloadStep currentStep = StartingStep;
public event PropertyChangedEventHandler PropertyChanged;
public IconDownloadStep CurrentStep
{
get => currentStep;
set
{
currentStep = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentStep)));
}
}
public abstract class IconDownloadStep : LoadStatus
{
public IconDownloadStep(string name, double progress) : base(name)
{
this.Progress = progress;
}
}
public class StoppedIconDownloadStep : IconDownloadStep
{
public StoppedIconDownloadStep(double progress) : base("Download stopped", progress)
{
}
}
public class DownloadingIconDownloadStep : IconDownloadStep
{
public DownloadingIconDownloadStep(string skillName, double progress) : base($"Downloading [{skillName}] icon", progress)
{
}
}
public class CheckingIconDownloadStep : IconDownloadStep
{
public CheckingIconDownloadStep(string skillName, double progress) : base($"Checking [{skillName}] icon", progress)
{
}
}
public class NotSupportedIconDownloadStep : IconDownloadStep
{
public NotSupportedIconDownloadStep() : base("Cannot download icons. The WebView2 browser is not supported", 0d)
{
}
}
public class FinishedIconDownloadStep : IconDownloadStep
{
public FinishedIconDownloadStep() : base("Download finished", 100d)
{
}
}
public class StartingIconDownloadStep : IconDownloadStep
{
public StartingIconDownloadStep() : base("Download starting", 0d)
{
}
}
}
}
+12
View File
@@ -0,0 +1,12 @@
namespace Daybreak.Models.Progress
{
public abstract class LoadStatus
{
public string Description { get; set; }
public double Progress { get; set; }
public LoadStatus(string description)
{
this.Description = description;
}
}
}
@@ -1,6 +1,6 @@
using System.ComponentModel;
namespace Daybreak.Models
namespace Daybreak.Models.Progress
{
public sealed class UpdateStatus : INotifyPropertyChanged
{
@@ -17,30 +17,26 @@ namespace Daybreak.Models
public UpdateStep CurrentStep
{
get => this.currentStep;
get => currentStep;
set
{
this.currentStep = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentStep)));
currentStep = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentStep)));
}
}
public class UpdateStep
public class UpdateStep : LoadStatus
{
public string Name { get; }
internal UpdateStep(string name)
public UpdateStep(string name) : base(name)
{
this.Name = name;
}
}
public class DownloadUpdateStep : UpdateStep
{
internal DownloadUpdateStep(string name, double progress) : base(name)
{
this.Progress = progress;
Progress = progress;
}
public double Progress { get; }
}
}
}
@@ -0,0 +1,17 @@
using Daybreak.Controls;
using Daybreak.Models;
using Microsoft.Web.WebView2.Wpf;
using System.Threading;
namespace Daybreak.Services.IconRetrieve
{
public interface IIconBrowser
{
void InitializeWebView(WebView2 webView2, CancellationToken cancellationToken);
/// <summary>
/// Queue an icon request. The browser will attempt to download the icon. Monitor the <see cref="IconRequest.Finished"/> to be notified when the request has been served.
/// </summary>
/// <param name="iconRequest">Request model.</param>
void QueueIconRequest(IconRequest iconRequest);
}
}
@@ -1,12 +1,12 @@
using Daybreak.Models.Builds;
using System;
using System.Extensions;
using System.IO;
using System.Threading.Tasks;
namespace Daybreak.Services.IconRetrieve
{
public interface IIconRetriever
public interface IIconCache
{
Task<Optional<Stream>> GetIcon(Skill skill);
Task<Optional<Uri>> GetIconUri(Skill skill);
}
}
@@ -0,0 +1,16 @@
using Daybreak.Controls;
using Daybreak.Models.Progress;
using Microsoft.Web.WebView2.Wpf;
using System.Threading.Tasks;
namespace Daybreak.Services.IconRetrieve
{
public interface IIconDownloader
{
void SetBrowser(WebView2 chromiumBrowserWrapper);
bool DownloadComplete { get; }
Task<IconDownloadStatus> StartIconDownload();
void CancelIconDownload();
}
}
@@ -0,0 +1,178 @@
using Daybreak.Controls;
using Daybreak.Models;
using Daybreak.Models.Builds;
using Daybreak.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Core.Extensions;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace Daybreak.Services.IconRetrieve
{
public sealed class IconBrowser : IIconBrowser
{
// Sometimes due to browser issues, retrieved base64 is just a blank jpeg. This is the base64 of the image.
private const string FaultyBase64 = "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAAHRyWFlaAAABZAAAABRnWFlaAAABeAAAABRiWFlaAAABjAAAABRyVFJDAAABoAAAAChnVFJDAAABoAAAAChiVFJDAAABoAAAACh3dHB0AAAByAAAABRjcHJ0AAAB3AAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAFgAAAAcAHMAUgBHAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z3BhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABYWVogAAAAAAAA9tYAAQAAAADTLW1sdWMAAAAAAAAAAQAAAAxlblVTAAAAIAAAABwARwBvAG8AZwBsAGUAIABJAG4AYwAuACAAMgAwADEANv/bAEMAAwICAgICAwICAgMDAwMEBgQEBAQECAYGBQYJCAoKCQgJCQoMDwwKCw4LCQkNEQ0ODxAQERAKDBITEhATDxAQEP/bAEMBAwMDBAMECAQECBALCQsQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEP/AABEIAEAAQAMBIgACEQEDEQH/xAAVAAEBAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AlUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//2Q==";
private const string LargeFaultyBase64 = "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAAHRyWFlaAAABZAAAABRnWFlaAAABeAAAABRiWFlaAAABjAAAABRyVFJDAAABoAAAAChnVFJDAAABoAAAAChiVFJDAAABoAAAACh3dHB0AAAByAAAABRjcHJ0AAAB3AAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAFgAAAAcAHMAUgBHAEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z3BhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABYWVogAAAAAAAA9tYAAQAAAADTLW1sdWMAAAAAAAAAAQAAAAxlblVTAAAAIAAAABwARwBvAG8AZwBsAGUAIABJAG4AYwAuACAAMgAwADEANv/bAEMAAwICAgICAwICAgMDAwMEBgQEBAQECAYGBQYJCAoKCQgJCQoMDwwKCw4LCQkNEQ0ODxAQERAKDBITEhATDxAQEP/bAEMBAwMDBAMECAQECBALCQsQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEP/AABEIAIAAQAMBIgACEQEDEQH/xAAVAAEBAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AlUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//2Q==";
private const string BaseUrl = "https://wiki.guildwars.com";
private const string QueryUrl = $"wiki/File:{NamePlaceholder}.jpg";
private const string NamePlaceholder = "[SKILLNAME]";
private const string IconsDirectoryName = "Icons";
private const string IconsLocation = $"{IconsDirectoryName}/{NamePlaceholder}.jpg";
private readonly ConcurrentQueue<IconRequest> iconRequests = new();
private readonly ILogger<IconBrowser> logger;
private WebView2 browserWrapper;
private CancellationToken cancellationToken;
public IconBrowser(
ILogger<IconBrowser> logger)
{
this.logger = logger.ThrowIfNull();
}
public void InitializeWebView(WebView2 webView2, CancellationToken cancellationToken)
{
this.browserWrapper = webView2.ThrowIfNull();
this.cancellationToken = cancellationToken;
Task.Run(this.PeriodicallyServeRequests, cancellationToken);
}
public void QueueIconRequest(IconRequest iconRequest)
{
this.iconRequests.Enqueue(iconRequest);
}
private async Task PeriodicallyServeRequests()
{
while(this.cancellationToken.IsCancellationRequested is false)
{
await Application.Current.Dispatcher.InvokeAsync(async () =>
{
await this.ServeRequest();
});
}
}
private async Task ServeRequest()
{
if (this.cancellationToken.IsCancellationRequested)
{
return;
}
if (this.iconRequests.TryDequeue(out var request) is false)
{
await Task.Delay(1000);
return;
}
var logger = this.logger.CreateScopedLogger(nameof(this.PeriodicallyServeRequests), request.Skill?.Name);
logger.LogInformation($"Retrieving icon");
while (this.browserWrapper is null)
{
if (this.cancellationToken.IsCancellationRequested)
{
return;
}
logger.LogInformation($"Browser is not yet initialized. Waiting");
await Task.Delay(1000);
}
try
{
await this.browserWrapper.EnsureCoreWebView2Async();
}
catch(Exception e)
{
}
var curedSkillName = request.Skill.AlternativeName.IsNullOrWhiteSpace() ?
request.Skill.Name.Replace(" ", "_") :
request.Skill.AlternativeName.Replace(" ", "_");
var skillIconUrl = $"{BaseUrl}/{QueryUrl.Replace(NamePlaceholder, curedSkillName)}";
logger.LogInformation($"Looking for icon at {skillIconUrl}");
this.browserWrapper.CoreWebView2.Navigate(skillIconUrl);
for (var i = 0; i < 5; i++)
{
if (this.cancellationToken.IsCancellationRequested)
{
return;
}
logger.LogInformation("Executing extraction script");
var responseTask = await Application.Current.Dispatcher.InvokeAsync(async () =>
{
return await this.browserWrapper.ExecuteScriptAsync(Scripts.GetHrefFromSkillPage);
});
var response = await responseTask;
logger.LogInformation("Parsing response");
var iconPayload = JsonConvert.DeserializeObject<IconPayload>(response);
if (iconPayload is null)
{
logger.LogInformation("Bad response");
await Task.Delay(1000);
continue;
}
if (iconPayload.SkillUrl != skillIconUrl.Replace("\"", "%22"))
{
logger.LogInformation("Retrieved icon doesn't match");
await Task.Delay(1000);
continue;
}
var potentialBase64 = iconPayload.SkillImage.Split(',').Skip(1).FirstOrDefault();
if (potentialBase64 == FaultyBase64 ||
potentialBase64 == LargeFaultyBase64)
{
logger.LogInformation("Faulty base64 retrieved");
await Task.Delay(1000);
continue;
}
byte[] bytes;
try
{
bytes = Convert.FromBase64String(potentialBase64);
}
catch
{
logger.LogError("Failed to parse base64");
await Task.Delay(1000);
continue;
}
await SaveIconLocally(request.Skill, bytes);
request.IconBase64 = potentialBase64;
request.Finished = true;
break;
}
logger.LogError($"Failed to retrieve icon");
request.Finished = true;
}
private static async Task<string> SaveIconLocally(Skill skill, byte[] data)
{
var curedSkillName = skill.Name
.Replace(" ", "_")
.Replace("\"", "");
await File.WriteAllBytesAsync(IconsLocation.Replace(NamePlaceholder, curedSkillName), data);
return curedSkillName;
}
}
}
@@ -0,0 +1,56 @@
using Daybreak.Models.Builds;
using Microsoft.Extensions.Logging;
using System;
using System.Core.Extensions;
using System.Extensions;
using System.IO;
using System.Threading.Tasks;
namespace Daybreak.Services.IconRetrieve
{
public sealed class IconCache : IIconCache
{
private const string NamePlaceholder = "[SKILLNAME]";
private const string IconsDirectoryName = "Icons";
private const string IconsLocation = $"{IconsDirectoryName}/{NamePlaceholder}.jpg";
private readonly ILogger<IconCache> logger;
public IconCache(
ILogger<IconCache> logger)
{
this.logger = logger.ThrowIfNull();
if (Directory.Exists(IconsDirectoryName) is false)
{
Directory.CreateDirectory(IconsDirectoryName);
}
}
public Task<Optional<Uri>> GetIconUri(Skill skill)
{
var maybeIconUri = this.GetLocalIcon(skill);
if (maybeIconUri.ExtractValue() is Uri uri)
{
return Task.FromResult(Optional.FromValue(uri));
}
return Task.FromResult(Optional.None<Uri>());
}
private Optional<Uri> GetLocalIcon(Skill skill)
{
var curedSkillName = skill.Name
.Replace(" ", "_")
.Replace("\"", "");
this.logger.LogInformation("Checking local icon cache");
if (File.Exists(IconsLocation.Replace(NamePlaceholder, curedSkillName)))
{
this.logger.LogInformation("Local icon cache found. Retrieving icon");
return new Uri(AppDomain.CurrentDomain.BaseDirectory + "/" + IconsLocation.Replace(NamePlaceholder, curedSkillName), UriKind.Absolute);
}
this.logger.LogWarning("No local icon cache found");
return Optional.None<Uri>();
}
}
}
@@ -0,0 +1,239 @@
using Daybreak.Configuration;
using Daybreak.Controls;
using Daybreak.Models;
using Daybreak.Models.Builds;
using Daybreak.Models.Progress;
using Daybreak.Services.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Core.Extensions;
using System.Extensions;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Extensions.Services;
namespace Daybreak.Services.IconRetrieve
{
public sealed class IconDownloader : IIconDownloader, IApplicationLifetimeService
{
private readonly IIconBrowser iconBrowser;
private readonly IIconCache iconCache;
private readonly IConfigurationManager configurationManager;
private readonly ILogger<IconDownloader> logger;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
private readonly ILogger<ChromiumBrowserWrapper> browserLogger;
private WebView2 browserWrapper;
private CancellationTokenSource cancellationTokenSource;
private IconDownloadStatus iconDownloadStatus;
public bool DownloadComplete { get; private set; }
public bool Downloading => this.cancellationTokenSource is not null;
public IconDownloader(
IIconBrowser iconBrowser,
IIconCache iconCache,
IConfigurationManager configurationManager,
ILogger<IconDownloader> logger,
ILiveOptions<ApplicationConfiguration> liveOptions,
ILogger<ChromiumBrowserWrapper> browserLogger)
{
this.iconBrowser = iconBrowser.ThrowIfNull();
this.iconCache = iconCache.ThrowIfNull();
this.configurationManager = configurationManager.ThrowIfNull();
this.logger = logger.ThrowIfNull();
this.liveOptions = liveOptions.ThrowIfNull();
this.browserLogger = browserLogger.ThrowIfNull();
this.HookIntoConfigurationChanges();
}
public void SetBrowser(WebView2 chromiumBrowserWrapper)
{
if (this.browserWrapper is not null)
{
throw new InvalidOperationException("Browser is already set");
}
this.browserWrapper = chromiumBrowserWrapper;
}
public async Task<IconDownloadStatus> StartIconDownload()
{
while(this.browserWrapper is null)
{
await Task.Delay(100);
}
this.logger.LogInformation("Starting download");
if (this.Downloading)
{
this.logger.LogInformation("Download already running");
return this.iconDownloadStatus;
}
this.cancellationTokenSource = new();
this.iconDownloadStatus = new IconDownloadStatus();
Task.Run(this.DownloadIcons);
return this.iconDownloadStatus;
}
public void CancelIconDownload()
{
this.cancellationTokenSource?.Cancel();
this.cancellationTokenSource?.Dispose();
this.cancellationTokenSource = null;
}
private async Task DownloadIcons()
{
this.logger.LogInformation("Beginning icon download");
if (await TestBrowserSupported() is false)
{
this.logger.LogError("Browser not supported. Icon downloading stopped");
this.iconDownloadStatus.CurrentStep = IconDownloadStatus.BrowserNotSupported;
return;
}
var progressIncrement = 100d / Skill.Skills.Count();
var progressValue = 0d;
var skillsToDownload = new List<Skill>();
foreach(var skill in Skill.Skills.OrderBy(s => s.Name))
{
if (skill == Skill.NoSkill)
{
continue;
}
var logger = this.logger.CreateScopedLogger(nameof(this.DownloadIcons), skill.Name);
logger.LogInformation("Verifying if icon exists");
this.iconDownloadStatus.CurrentStep = IconDownloadStatus.Checking(skill.Name, progressValue);
if ((await this.iconCache.GetIconUri(skill)).ExtractValue() is not null)
{
progressValue += progressIncrement;
await Task.Delay(1);
continue;
}
skillsToDownload.Add(skill);
}
if (skillsToDownload.Count == 0)
{
this.logger.LogInformation("No icons missing. Stopping download");
this.iconDownloadStatus.CurrentStep = IconDownloadStatus.Finished;
return;
}
this.iconBrowser.InitializeWebView(this.browserWrapper, this.cancellationTokenSource.Token);
var incomplete = false;
foreach (var skill in skillsToDownload)
{
if (this.cancellationTokenSource?.IsCancellationRequested is null or true)
{
this.iconDownloadStatus.CurrentStep = IconDownloadStatus.Stopped(progressValue);
return;
}
if (skill == Skill.NoSkill)
{
progressValue += progressIncrement;
continue;
}
logger.LogInformation("Downloading icon");
this.iconDownloadStatus.CurrentStep = IconDownloadStatus.Downloading(skill.Name, progressValue);
var request = new IconRequest { Skill = skill };
this.iconBrowser.QueueIconRequest(request);
while (request.Finished is false)
{
await Task.Delay(1000);
}
if (request.IconBase64.IsNullOrWhiteSpace())
{
logger.LogWarning("Failed to download icon");
incomplete = true;
}
else
{
logger.LogInformation("Downloaded icon");
}
progressValue += progressIncrement;
}
if (incomplete)
{
this.logger.LogError("Failed to download all icons. Retrying");
await this.DownloadIcons();
}
else
{
Application.Current.Dispatcher.Invoke(() =>
{
this.browserWrapper.IsEnabled = false;
this.browserWrapper.Dispose();
});
this.iconDownloadStatus.CurrentStep = IconDownloadStatus.Finished;
this.DownloadComplete = true;
}
}
private static async Task<bool> TestBrowserSupported()
{
CoreWebView2Environment coreWebView2Environment;
try
{
var task = await Application.Current.Dispatcher.InvokeAsync(async () =>
{
return await CoreWebView2Environment.CreateAsync().ConfigureAwait(true);
});
coreWebView2Environment = await task;
}
catch (Exception)
{
return false;
}
return coreWebView2Environment is not null;
}
private void HookIntoConfigurationChanges()
{
this.configurationManager.ConfigurationChanged += async (_, _) =>
{
var configuration = this.configurationManager.GetConfiguration();
if (configuration.ExperimentalFeatures.DownloadIcons)
{
await this.StartIconDownload();
}
else
{
this.CancelIconDownload();
}
};
}
public async void OnStartup()
{
var configuration = this.configurationManager.GetConfiguration();
if (configuration.ExperimentalFeatures.DownloadIcons)
{
await this.StartIconDownload();
}
}
public void OnClosing()
{
this.cancellationTokenSource?.Cancel();
}
}
}
@@ -1,140 +0,0 @@
using Daybreak.Configuration;
using Daybreak.Models.Builds;
using HtmlAgilityPack;
using Microsoft.Extensions.Logging;
using System;
using System.Configuration;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
namespace Daybreak.Services.IconRetrieve
{
public sealed class IconRetriever : IIconRetriever
{
private const string NamePlaceholder = "[SKILLNAME]";
private const string BaseUrl = "https://wiki.guildwars.com";
private const string QueryUrl = $"wiki/File:{NamePlaceholder}.jpg";
private const string IconsDirectoryName = "Icons";
private const string IconsLocation = $"{IconsDirectoryName}/{NamePlaceholder}.jpg";
private readonly IHttpClient<IconRetriever> httpClient;
private readonly ILogger<IconRetriever> logger;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
public IconRetriever(
ILogger<IconRetriever> logger,
IHttpClient<IconRetriever> httpClient,
ILiveOptions<ApplicationConfiguration> liveOptions)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.httpClient = httpClient.ThrowIfNull(nameof(httpClient));
this.liveOptions = liveOptions.ThrowIfNull(nameof(liveOptions));
this.httpClient.BaseAddress = new Uri(BaseUrl);
if (Directory.Exists(IconsDirectoryName) is false)
{
Directory.CreateDirectory(IconsDirectoryName);
}
}
public async Task<Optional<Stream>> GetIcon(Skill skill)
{
if (this.liveOptions.Value.KeepLocalIconCache)
{
this.logger.LogInformation($"{nameof(IconRetriever)} configured to look first in cache before downloading icons");
var maybeIcon = await this.GetLocalIcon(skill);
if (maybeIcon.ExtractValue() is Stream stream)
{
return stream;
}
}
else
{
this.logger.LogInformation($"{nameof(IconRetriever)} configured to skip local cache. Downloading icon");
}
return await this.DownloadIcon(skill);
}
private async Task<Optional<Stream>> DownloadIcon(Skill skill)
{
var curedSkillName = skill.Name
.Replace(" ", "_");
var skillIconUrl = QueryUrl.Replace(NamePlaceholder, curedSkillName);
this.logger.LogInformation($"Looking up icon for skill '{skill.Name}' at url {skillIconUrl}");
using var response = await this.httpClient.GetAsync(skillIconUrl).ConfigureAwait(false);
if (response.IsSuccessStatusCode is false)
{
this.logger.LogError($"Client returned status code {response.StatusCode}");
return Optional.None<Stream>();
}
this.logger.LogInformation("Crawling through response for href to latest icon url");
var doc = new HtmlDocument();
doc.LoadHtml(await response.Content.ReadAsStringAsync());
var url = GetHref(doc);
if (url is null)
{
this.logger.LogError("Failed to find latest icon url");
return Optional.None<Stream>();
}
this.logger.LogInformation($"Found latest icon url at {BaseUrl + "/" + url}. Requesting stream");
using var iconResponse = await this.httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
this.logger.LogInformation("Retrieved latest icon stream");
var iconData = await iconResponse.Content.ReadAsByteArrayAsync();
if (this.liveOptions.Value.KeepLocalIconCache)
{
await SaveIconLocally(skill, iconData);
}
return new MemoryStream(iconData);
}
this.logger.LogError($"Failed to retrieve icon from {BaseUrl + "/" + url}");
return Optional.None<Stream>();
}
private async Task<Optional<Stream>> GetLocalIcon(Skill skill)
{
var curedSkillName = skill.Name
.Replace(" ", "_")
.Replace("\"", "");
this.logger.LogInformation("Checking local icon cache");
if (File.Exists(IconsLocation.Replace(NamePlaceholder, curedSkillName)))
{
this.logger.LogInformation("Local icon cache found. Retrieving icon");
return new MemoryStream(await File.ReadAllBytesAsync(IconsLocation.Replace(NamePlaceholder, curedSkillName)));
}
this.logger.LogWarning("No local icon cache found");
return Optional.None<Stream>();
}
private static async Task SaveIconLocally(Skill skill, byte[] data)
{
var curedSkillName = skill.Name
.Replace(" ", "_")
.Replace("\"", "");
await File.WriteAllBytesAsync(IconsLocation.Replace(NamePlaceholder, curedSkillName), data);
}
private static string GetHref(HtmlDocument doc)
{
foreach (var child in doc.DocumentNode.Descendants("a"))
{
var targetAttribute = child.Attributes.Where(a => a.Name == "href" && a.Value.Contains("images")).FirstOrDefault();
if (targetAttribute is not null)
{
return targetAttribute.Value;
}
}
return null;
}
}
}
@@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Logging;
namespace Daybreak.Services.Logging
{
public sealed class CompositeLogsWriter : ILogsWriter
{
private readonly IEnumerable<ILogsWriter> logsWriters;
public CompositeLogsWriter(params ILogsWriter[] innerLogsWriters)
{
this.logsWriters = innerLogsWriters;
}
public void WriteLog(Log log)
{
foreach (var logWriter in this.logsWriters)
{
logWriter.WriteLog(log);
}
}
}
}
@@ -0,0 +1,13 @@
using System.Diagnostics;
using System.Logging;
namespace Daybreak.Services.Logging
{
public sealed class DebugLogsWriter : IDebugLogsWriter
{
public void WriteLog(Log log)
{
Debug.WriteLine($"[{log.LogTime}]\t[{log.LogLevel}]\t[{log.Category}]\n{log.Message}");
}
}
}
@@ -0,0 +1,8 @@
using System.Logging;
namespace Daybreak.Services.Logging
{
public interface IDebugLogsWriter : ILogsWriter
{
}
}
@@ -1,7 +1,7 @@
using Daybreak.Configuration;
using Daybreak.Exceptions;
using Daybreak.Models;
using Daybreak.Models.Github;
using Daybreak.Models.Progress;
using Daybreak.Services.ViewManagement;
using Daybreak.Views;
using Microsoft.Extensions.Logging;
@@ -1,4 +1,4 @@
using Daybreak.Models;
using Daybreak.Models.Progress;
using Daybreak.Models.Versioning;
using System.Collections.Generic;
using System.Threading.Tasks;
+27
View File
@@ -31,5 +31,32 @@
};
window.chrome.webview.postMessage(jsonObject);
});";
public const string GetHrefFromSkillPage = @"
new function(){
var img = document.getElementsByClassName('fullImageLink')[0].childNodes[0].childNodes[0];
function getDataUrl(img) {
// Create canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set width and height
canvas.width = img.width;
canvas.height = img.height;
// Draw the image
ctx.drawImage(img, 0, 0);
return canvas.toDataURL('image/jpeg');
}
console.log(img.src);
var imageBase64 = getDataUrl(img);
console.log(imageBase64);
let jsonObject =
{
skillUrl: document.URL,
skillImage: imageBase64
}
window.chrome.webview.postMessage(jsonObject);
return jsonObject;
}";
}
}
+53
View File
@@ -0,0 +1,53 @@
using System;
using System.Linq;
namespace Utils
{
public static class StringUtils
{
public static int DamerauLevenshteinDistance(string s, string t)
{
var (height, width) = (s.Length + 1, t.Length + 1);
var matrix = new int[height, width];
for (var j = 0; j < height; j++) { matrix[j, 0] = j; };
for (var i = 0; i < width; i++) { matrix[0, i] = i; };
for (var j = 1; j < height; j++)
{
for (var i = 1; i < width; i++)
{
var cost = (s[j - 1] == t[i - 1]) ? 0 : 1;
var insertion = matrix[j, i - 1] + 1;
var deletion = matrix[j - 1, i] + 1;
var substitution = matrix[j - 1, i - 1] + cost;
var distance = Math.Min(insertion, Math.Min(deletion, substitution));
if (j > 1 && i > 1 && s[j - 1] == t[i - 2] && s[j - 2] == t[i - 1])
{
distance = Math.Min(distance, matrix[j - 2, i - 2] + cost);
}
matrix[j, i] = distance;
}
}
return matrix[height - 1, width - 1];
}
/// <summary>
/// Returns true if stringToSearch is somewhat close to searchString.
/// </summary>
/// <param name="stringToSearch"></param>
/// <param name="searchString"></param>
/// <returns>True if strings match.</returns>
public static bool MatchesSearchString(string stringToSearch, string searchString)
{
// Return true if either the distance between the entire text and the searchstring is small enough, or if any of the words are close to the search string.
return DamerauLevenshteinDistance(stringToSearch.ToLower()[..Math.Min(stringToSearch.Length, searchString.Length)], searchString.ToLower()) < 3 ||
stringToSearch.Split(' ').Any(word => DamerauLevenshteinDistance(word.ToLower()[..Math.Min(word.Length, searchString.Length)], searchString.ToLower()) < 3);
}
}
}
+3 -2
View File
@@ -38,7 +38,8 @@ namespace Daybreak.Views
public BuildTemplateView(
IViewManager viewManager,
IBuildTemplateManager buildTemplateManager,
IIconRetriever iconRetriever,
IIconCache iconRetriever,
IIconBrowser iconBrowser,
ILiveOptions<ApplicationConfiguration> liveOptions,
ILogger<ChromiumBrowserWrapper> chromiumLogger,
ILogger<BuildTemplateView> logger)
@@ -47,7 +48,7 @@ namespace Daybreak.Views
this.logger = logger.ThrowIfNull(nameof(logger));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
this.BuildTemplate.InitializeTemplate(iconRetriever, liveOptions, buildTemplateManager, chromiumLogger);
this.BuildTemplate.InitializeTemplate(iconRetriever, iconBrowser, liveOptions, buildTemplateManager, chromiumLogger);
this.DataContextChanged += (sender, contextArgs) =>
{
if (contextArgs.NewValue is BuildEntry)
+6 -1
View File
@@ -10,6 +10,7 @@
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="#A0202020">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
@@ -17,7 +18,11 @@
Clicked="BackButton_Clicked"></controls:BackButton>
<controls:AddButton Foreground="White" Height="30" Width="30" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="5"
Clicked="AddButton_Clicked"></controls:AddButton>
<ListView Grid.Row="1" Background="Transparent" ItemsSource="{Binding ElementName=_this, Path=BuildEntries, Mode=OneWay}"
<controls:SearchTextBox Grid.Row="1"
FontSize="24"
Foreground="White"
TextChanged="SearchTextBox_TextChanged"></controls:SearchTextBox>
<ListView Grid.Row="2" Background="Transparent" ItemsSource="{Binding ElementName=_this, Path=BuildEntries, Mode=OneWay}"
MouseDoubleClick="ListView_MouseDoubleClick" HorizontalContentAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate>
+14 -1
View File
@@ -2,9 +2,12 @@
using Daybreak.Services.BuildTemplates;
using Daybreak.Services.ViewManagement;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Extensions;
using System.Linq;
using System.Windows.Controls;
using Utils;
namespace Daybreak.Views
{
@@ -16,6 +19,8 @@ namespace Daybreak.Views
private readonly IViewManager viewManager;
private readonly IBuildTemplateManager buildTemplateManager;
private IEnumerable<BuildEntry> buildEntries;
public ObservableCollection<BuildEntry> BuildEntries { get; } = new ObservableCollection<BuildEntry>();
public BuildsListView(
@@ -30,7 +35,8 @@ namespace Daybreak.Views
private void LoadBuilds()
{
this.BuildEntries.ClearAnd().AddRange(this.buildTemplateManager.GetBuilds());
this.buildEntries = this.buildTemplateManager.GetBuilds();
this.BuildEntries.ClearAnd().AddRange(this.buildEntries);
}
private void ListView_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
@@ -54,5 +60,12 @@ namespace Daybreak.Views
this.buildTemplateManager.RemoveBuild(e);
this.LoadBuilds();
}
private void SearchTextBox_TextChanged(object sender, string e)
{
this.BuildEntries.Clear();
this.BuildEntries.AddRange(
this.buildEntries.Where(b => StringUtils.MatchesSearchString(b.Name, e)));
}
}
}
@@ -92,6 +92,7 @@
<TextBlock Text="GWToolbox startup delay (in ms)" Foreground="White" FontSize="22" Height="30"></TextBlock>
<TextBlock Text="Detect build templates (in browser)" Foreground="White" FontSize="22" Height="30"></TextBlock>
<TextBlock Text="Launch gw as current user" Foreground="White" FontSize="22" Height="30"></TextBlock>
<TextBlock Text="Download icons" Foreground="White" FontSize="22" Height="30"></TextBlock>
</StackPanel>
<StackPanel Grid.Column="1">
<ToggleButton IsChecked="{Binding ElementName=_this, Path=MultiLaunch, Mode=TwoWay}" Style="{StaticResource AnimatedSwitch}"
@@ -103,6 +104,8 @@
Height="30" Width="60"></ToggleButton>
<ToggleButton IsChecked="{Binding ElementName=_this, Path=LaunchAsCurrentUser, Mode=TwoWay}" Style="{StaticResource AnimatedSwitch}"
Height="30" Width="60"></ToggleButton>
<ToggleButton IsChecked="{Binding ElementName=_this, Path=DownloadIcons, Mode=TwoWay}" Style="{StaticResource AnimatedSwitch}"
Height="30" Width="60"></ToggleButton>
</StackPanel>
</Grid>
</Grid>
+14 -36
View File
@@ -16,46 +16,22 @@ namespace Daybreak.Views
/// </summary>
public partial class ExperimentalSettingsView : UserControl
{
public static readonly DependencyProperty MultiLaunchProperty =
DependencyPropertyExtensions.Register<ExperimentalSettingsView, bool>(nameof(MultiLaunch));
public static readonly DependencyProperty GWToolboxLaunchDelayProperty =
DependencyPropertyExtensions.Register<ExperimentalSettingsView, string>(nameof(GWToolboxLaunchDelay));
public static readonly DependencyProperty DynamicBuildLoadingProperty =
DependencyPropertyExtensions.Register<ExperimentalSettingsView, bool>(nameof(DynamicBuildLoading));
public static readonly DependencyProperty LaunchAsCurrentUserProperty =
DependencyPropertyExtensions.Register<ExperimentalSettingsView, bool>(nameof(LaunchAsCurrentUser));
public static readonly DependencyProperty MacrosEnabledProperty =
DependencyPropertyExtensions.Register<ExperimentalSettingsView, bool>(nameof(MacrosEnabled));
[GenerateDependencyProperty]
private bool launchAsCurrentUser;
[GenerateDependencyProperty]
private bool multiLaunch;
[GenerateDependencyProperty]
private bool dynamicBuildLoading;
[GenerateDependencyProperty]
private bool macrosEnabled;
[GenerateDependencyProperty]
public string gWToolboxLaunchDelay;
[GenerateDependencyProperty]
public bool downloadIcons;
private readonly IViewManager viewManager;
private readonly ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions;
public bool LaunchAsCurrentUser
{
get => this.GetTypedValue<bool>(LaunchAsCurrentUserProperty);
set => this.SetValue(LaunchAsCurrentUserProperty, value);
}
public bool MultiLaunch
{
get => this.GetTypedValue<bool>(MultiLaunchProperty);
set => this.SetValue(MultiLaunchProperty, value);
}
public string GWToolboxLaunchDelay
{
get => this.GetTypedValue<string>(GWToolboxLaunchDelayProperty);
set => this.SetValue(GWToolboxLaunchDelayProperty, value);
}
public bool DynamicBuildLoading
{
get => this.GetTypedValue<bool>(DynamicBuildLoadingProperty);
set => this.SetValue(DynamicBuildLoadingProperty, value);
}
public bool MacrosEnabled
{
get => this.GetTypedValue<bool>(MacrosEnabledProperty);
set => this.SetValue(MacrosEnabledProperty, value);
}
public ExperimentalSettingsView(
IViewManager viewManager,
ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions)
@@ -74,6 +50,7 @@ namespace Daybreak.Views
this.DynamicBuildLoading = config.ExperimentalFeatures.DynamicBuildLoading;
this.LaunchAsCurrentUser = config.ExperimentalFeatures.LaunchGuildwarsAsCurrentUser;
this.MacrosEnabled = config.ExperimentalFeatures.CanInterceptKeys;
this.DownloadIcons = config.ExperimentalFeatures.DownloadIcons;
}
private void SaveExperimentalSettings()
@@ -83,6 +60,7 @@ namespace Daybreak.Views
config.ExperimentalFeatures.DynamicBuildLoading = this.DynamicBuildLoading;
config.ExperimentalFeatures.LaunchGuildwarsAsCurrentUser = this.LaunchAsCurrentUser;
config.ExperimentalFeatures.CanInterceptKeys = this.MacrosEnabled;
config.ExperimentalFeatures.DownloadIcons = this.DownloadIcons;
if (int.TryParse(this.GWToolboxLaunchDelay, out var gwToolboxLaunchDelay))
{
config.ExperimentalFeatures.ToolboxAutoLaunchDelay = gwToolboxLaunchDelay;
+24
View File
@@ -0,0 +1,24 @@
<UserControl x:Class="Daybreak.Views.IconDownloadView"
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:local="clr-namespace:Daybreak.Views"
xmlns:controls="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
Loaded="IconDownloadView_Loaded"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"></BooleanToVisibilityConverter>
</UserControl.Resources>
<Grid VerticalAlignment="Center" HorizontalAlignment="Center" Background="White" MinHeight="200" MinWidth="400">
<StackPanel VerticalAlignment="Center">
<TextBlock Text="{Binding ElementName=_this, Path=Description, Mode=OneWay}" Margin="10, 0, 10, 0" Foreground="Black"
TextWrapping="Wrap" HorizontalAlignment="Center" FontSize="16"></TextBlock>
<ProgressBar Minimum="0" Maximum="100" Value="{Binding ElementName=_this, Path=ProgressValue, Mode=OneWay}" Width="300" Height="20"></ProgressBar>
<controls:OpaqueButton Text="Continue" BackgroundOpacity="0.2" TransparentBackground="Gray" HorizontalAlignment="Center" Margin="10" Width="80" Height="25"
Clicked="OpaqueButton_Clicked" Foreground="Black" FontSize="16"></controls:OpaqueButton>
</StackPanel>
</Grid>
</UserControl>
+68
View File
@@ -0,0 +1,68 @@
using Daybreak.Models.Progress;
using Daybreak.Services.IconRetrieve;
using Daybreak.Services.ViewManagement;
using Microsoft.Extensions.Logging;
using System;
using System.Core.Extensions;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for UpdateView.xaml
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Fields used by source generator for DependencyProperty")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used by source generators")]
public partial class IconDownloadView : UserControl
{
private readonly IIconDownloader iconDownloader;
private readonly IViewManager viewManager;
private readonly ILogger<IconDownloadView> logger;
[GenerateDependencyProperty]
private string description;
[GenerateDependencyProperty]
private double progressValue;
public IconDownloadView(
IIconDownloader iconDownloader,
IViewManager viewManager,
ILogger<IconDownloadView> logger)
{
this.iconDownloader = iconDownloader.ThrowIfNull();
this.viewManager = viewManager.ThrowIfNull();
this.logger = logger.ThrowIfNull();
this.InitializeComponent();
}
private void OpaqueButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<BuildsListView>();
}
private async void IconDownloadView_Loaded(object sender, RoutedEventArgs e)
{
var iconDownloadStatus = await this.iconDownloader.StartIconDownload().ConfigureAwait(true);
if (iconDownloadStatus.CurrentStep is IconDownloadStatus.FinishedIconDownloadStep or
IconDownloadStatus.StoppedIconDownloadStep)
{
this.viewManager.ShowView<BuildsListView>();
}
iconDownloadStatus.PropertyChanged += (_, _) =>
{
this.Dispatcher.Invoke(() =>
{
this.Description = iconDownloadStatus.CurrentStep.Description;
this.ProgressValue = iconDownloadStatus.CurrentStep.Progress;
});
};
this.Description = iconDownloadStatus.CurrentStep.Description;
this.ProgressValue = iconDownloadStatus.CurrentStep.Progress;
}
}
}
+6 -4
View File
@@ -73,13 +73,13 @@ namespace Daybreak.Views
this.InitializeComponent();
this.PeriodicallyCheckGameState();
this.InitializeBrowsers();
this.NavigateToDefaults();
}
private void InitializeBrowsers()
private async void InitializeBrowsers()
{
this.LeftWebBrowser.InitializeBrowser(this.liveOptions, this.buildTemplateManager, this.browserLogger);
this.RightWebBrowser.InitializeBrowser(this.liveOptions, this.buildTemplateManager, this.browserLogger);
await this.LeftWebBrowser.InitializeBrowser(this.liveOptions, this.buildTemplateManager, this.browserLogger);
await this.RightWebBrowser.InitializeBrowser(this.liveOptions, this.buildTemplateManager, this.browserLogger);
this.NavigateToDefaults();
}
private void NavigateToDefaults()
@@ -91,6 +91,8 @@ namespace Daybreak.Views
this.RightBrowserFavoriteAddress = applicationConfiguration.RightBrowserDefault;
this.LeftBrowserAddress = applicationConfiguration.LeftBrowserDefault;
this.RightBrowserAddress = applicationConfiguration.RightBrowserDefault;
this.LeftWebBrowser.WebBrowser.CoreWebView2.Navigate(applicationConfiguration.LeftBrowserDefault);
this.RightWebBrowser.WebBrowser.CoreWebView2.Navigate(applicationConfiguration.RightBrowserDefault);
}
else
{
+16 -3
View File
@@ -1,4 +1,7 @@
using Daybreak.Services.ViewManagement;
using Daybreak.Configuration;
using Daybreak.Services.ViewManagement;
using System.Configuration;
using System.Core.Extensions;
using System.Extensions;
using System.Windows.Controls;
@@ -10,11 +13,14 @@ namespace Daybreak.Views
public partial class SettingsCategoryView : UserControl
{
private readonly IViewManager viewManager;
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
public SettingsCategoryView(
ILiveOptions<ApplicationConfiguration> liveOptions,
IViewManager viewManager)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.liveOptions = liveOptions.ThrowIfNull();
this.viewManager = viewManager.ThrowIfNull();
InitializeComponent();
}
@@ -35,7 +41,14 @@ namespace Daybreak.Views
private void BuildsButton_Clicked(object sender, System.EventArgs e)
{
this.viewManager.ShowView<BuildsListView>();
if (this.liveOptions.Value.ExperimentalFeatures.DownloadIcons)
{
this.viewManager.ShowView<IconDownloadView>();
}
else
{
this.viewManager.ShowView<BuildsListView>();
}
}
private void VersionButton_Clicked(object sender, System.EventArgs e)
-3
View File
@@ -94,7 +94,6 @@
<TextBlock Text="Desired screen id: " FontSize="22" Foreground="White" Height="30"></TextBlock>
<TextBlock Text="Place Shortcut: " FontSize="22" Foreground="White" Height="30"></TextBlock>
<TextBlock Text="Shortcut folder: " FontSize="22" Foreground="White" Height="30"></TextBlock>
<TextBlock Text="Keep local cache of icons: " FontSize="22" Foreground="White" Height="30"></TextBlock>
</StackPanel>
<StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="1">
<ToggleButton HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
@@ -149,8 +148,6 @@
Foreground="White" BorderBrush="Gray" BorderThickness="0"
Clicked="ShortcutFolderPickerGlyph_Clicked"></controls:FilePickerGlyph>
</Grid>
<ToggleButton HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
FontSize="22" Foreground="White" IsChecked="{Binding ElementName=_this, Path=KeepLocalIconCache, Mode=TwoWay}"></ToggleButton>
</StackPanel>
</Grid>
</ScrollViewer>
-4
View File
@@ -42,8 +42,6 @@ namespace Daybreak.Views
private bool shortcutPlaced;
[GenerateDependencyProperty]
private bool autoCheckUpdate;
[GenerateDependencyProperty]
private bool keepLocalIconCache;
public SettingsView(
ILiveUpdateableOptions<ApplicationConfiguration> liveUpdateableOptions,
@@ -70,7 +68,6 @@ namespace Daybreak.Views
this.ShortcutFolder = config.ShortcutLocation;
this.ShortcutPlaced = config.PlaceShortcut;
this.AutoCheckUpdate = config.AutoCheckUpdate;
this.KeepLocalIconCache = config.KeepLocalIconCache;
}
private void SaveButton_Clicked(object sender, EventArgs e)
@@ -88,7 +85,6 @@ namespace Daybreak.Views
currentConfig.ShortcutLocation = this.ShortcutFolder;
currentConfig.PlaceShortcut = this.ShortcutPlaced;
currentConfig.AutoCheckUpdate = this.AutoCheckUpdate;
currentConfig.KeepLocalIconCache = this.KeepLocalIconCache;
this.liveUpdateableOptions.UpdateOption();
this.viewManager.ShowView<SettingsCategoryView>();
}
+2 -2
View File
@@ -1,4 +1,4 @@
using Daybreak.Models;
using Daybreak.Models.Progress;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Microsoft.Extensions.Logging;
@@ -51,7 +51,7 @@ namespace Daybreak.Views
this.ProgressValue = downloadUpdateStep.Progress * 100;
}
this.Description = this.updateStatus.CurrentStep.Name;
this.Description = this.updateStatus.CurrentStep.Description;
});
}
+2 -1
View File
@@ -4,6 +4,7 @@ using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Extensions;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Extensions;
using Version = Daybreak.Models.Versioning.Version;
@@ -39,7 +40,7 @@ namespace Daybreak.Views
private async void LoadVersionList()
{
this.Versions.ClearAnd().AddRange(await this.applicationUpdater.GetVersions());
this.Versions.ClearAnd().AddRange((await this.applicationUpdater.GetVersions()).Reverse());
}
private void CurrentVersion_Clicked(object sender, EventArgs e)
+22
View File
@@ -0,0 +1,22 @@
Param(
[Parameter(Mandatory=$true)]
[string]$currentVersion,
[Parameter(Mandatory=$true)]
[string]$lastVersion
)
if ($currentVersion.StartsWith("v")){
$currentVersion = $currentVersion.Substring(1)
}
if ($lastVersion.StartsWith("v")){
$lastVersion = $lastVersion.Substring(1)
}
$isNewer = $currentVersion.CompareTo($lastVersion) -eq 1
if ($isNewer -eq $false){
throw "Version is not incremented. Current version " + $currentVersion + ". Last version " + $lastVersion
}
else{
Write-Host "Version has been incremented"
}