mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-09-16 13:29:30 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f83d6c860 | ||
|
|
3db4975b48 | ||
|
|
6a6eea5d02 | ||
|
|
8fe9eeb32f | ||
|
|
4c1025243b | ||
|
|
f24eb02ca1 | ||
|
|
b01c09491f |
@@ -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 }}
|
||||
@@ -25,6 +25,11 @@ catch
|
||||
}
|
||||
Console.WriteLine("Deleting package");
|
||||
File.Delete(tempFile);
|
||||
|
||||
Console.WriteLine("Deleting browser caches");
|
||||
Directory.Delete("BrowserData", true);
|
||||
Directory.Delete("Daybreak.exe.WebView2", true);
|
||||
|
||||
Console.WriteLine("Launching application");
|
||||
var process = new Process
|
||||
{
|
||||
|
||||
+9
-1
@@ -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
|
||||
|
||||
@@ -22,7 +22,7 @@ using LiteDB;
|
||||
using Daybreak.Services.Options;
|
||||
using Daybreak.Models;
|
||||
using Microsoft.CorrelationVector;
|
||||
using Services.IconRetrieve;
|
||||
using System.Logging;
|
||||
|
||||
namespace Daybreak.Configuration
|
||||
{
|
||||
@@ -46,23 +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)
|
||||
@@ -83,6 +95,7 @@ namespace Daybreak.Configuration
|
||||
viewProducer.RegisterView<ScreenChoiceView>();
|
||||
viewProducer.RegisterView<VersionManagementView>();
|
||||
viewProducer.RegisterView<LogsView>();
|
||||
viewProducer.RegisterView<IconDownloadView>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,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>
|
||||
|
||||
@@ -3,7 +3,6 @@ using Daybreak.Models.Builds;
|
||||
using Daybreak.Services.BuildTemplates;
|
||||
using Daybreak.Services.IconRetrieve;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Services.IconRetrieve;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -15,6 +14,7 @@ using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Extensions;
|
||||
using Utils;
|
||||
|
||||
namespace Daybreak.Controls
|
||||
{
|
||||
@@ -37,6 +37,8 @@ namespace Daybreak.Controls
|
||||
|
||||
public event EventHandler BuildChanged;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private string skillSearchText;
|
||||
[GenerateDependencyProperty]
|
||||
private Profession primaryProfession;
|
||||
[GenerateDependencyProperty]
|
||||
@@ -68,16 +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.iconBrowser = iconBrowser.ThrowIfNull();
|
||||
this.SkillBrowser.InitializeBrowser(liveOptions, buildTemplateManager, logger);
|
||||
this.iconBrowser.InitializeWebView(this.SkillBrowser.WebBrowser, this.cancellationTokenSource.Token);
|
||||
await this.SkillBrowser.InitializeBrowser(liveOptions, buildTemplateManager, logger);
|
||||
this.SkillTemplate0.InitializeSkillTemplate(iconRetriever);
|
||||
this.SkillTemplate1.InitializeSkillTemplate(iconRetriever);
|
||||
this.SkillTemplate2.InitializeSkillTemplate(iconRetriever);
|
||||
@@ -86,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)
|
||||
@@ -209,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);
|
||||
|
||||
@@ -299,7 +306,7 @@ namespace Daybreak.Controls
|
||||
if (this.SkillBrowser.BrowserSupported is true)
|
||||
{
|
||||
this.SkillBrowser.Width = 400;
|
||||
this.SkillsListView.Width = 0;
|
||||
this.SkillListContainer.Width = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,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)
|
||||
@@ -365,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>();
|
||||
}
|
||||
@@ -376,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;
|
||||
@@ -417,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,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;
|
||||
@@ -32,13 +32,19 @@ 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 async void SkillTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (this.iconRetriever is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.NewValue is Skill skill)
|
||||
{
|
||||
if (skill != Skill.NoSkill)
|
||||
|
||||
@@ -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,7 +10,7 @@
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
|
||||
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
|
||||
<Version>0.9.3.3</Version>
|
||||
<Version>0.9.4.2</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -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'">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Models
|
||||
namespace Daybreak.Models
|
||||
{
|
||||
public sealed class IconPayload
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Daybreak.Models.Builds;
|
||||
|
||||
namespace Models
|
||||
namespace Daybreak.Models
|
||||
{
|
||||
public sealed class IconRequest
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
using Microsoft.Web.WebView2.Wpf;
|
||||
using Models;
|
||||
using Daybreak.Controls;
|
||||
using Daybreak.Models;
|
||||
using Microsoft.Web.WebView2.Wpf;
|
||||
using System.Threading;
|
||||
|
||||
namespace Services.IconRetrieve
|
||||
namespace Daybreak.Services.IconRetrieve
|
||||
{
|
||||
public interface IIconBrowser
|
||||
{
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Daybreak.Services.IconRetrieve
|
||||
{
|
||||
public interface IIconRetriever
|
||||
public interface IIconCache
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
using Daybreak.Utils;
|
||||
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 Models;
|
||||
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;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Services.IconRetrieve
|
||||
namespace Daybreak.Services.IconRetrieve
|
||||
{
|
||||
public sealed class IconBrowser : IIconBrowser
|
||||
{
|
||||
@@ -23,10 +26,12 @@ namespace Services.IconRetrieve
|
||||
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 webView2;
|
||||
private WebView2 browserWrapper;
|
||||
private CancellationToken cancellationToken;
|
||||
|
||||
public IconBrowser(
|
||||
@@ -37,7 +42,7 @@ namespace Services.IconRetrieve
|
||||
|
||||
public void InitializeWebView(WebView2 webView2, CancellationToken cancellationToken)
|
||||
{
|
||||
this.webView2 = webView2.ThrowIfNull();
|
||||
this.browserWrapper = webView2.ThrowIfNull();
|
||||
this.cancellationToken = cancellationToken;
|
||||
Task.Run(this.PeriodicallyServeRequests, cancellationToken);
|
||||
}
|
||||
@@ -49,89 +54,125 @@ namespace Services.IconRetrieve
|
||||
|
||||
private async Task PeriodicallyServeRequests()
|
||||
{
|
||||
while(true)
|
||||
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;
|
||||
}
|
||||
|
||||
if (this.iconRequests.TryDequeue(out var request) is false)
|
||||
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;
|
||||
}
|
||||
|
||||
this.logger.LogInformation($"Retrieving {request.Skill.Name} icon");
|
||||
while(this.webView2 is null)
|
||||
if (iconPayload.SkillUrl != skillIconUrl.Replace("\"", "%22"))
|
||||
{
|
||||
if (this.cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.LogInformation($"Browser is not yet initialized. Waiting");
|
||||
logger.LogInformation("Retrieved icon doesn't match");
|
||||
await Task.Delay(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
await Application.Current.Dispatcher.InvokeAsync(async () =>
|
||||
var potentialBase64 = iconPayload.SkillImage.Split(',').Skip(1).FirstOrDefault();
|
||||
if (potentialBase64 == FaultyBase64 ||
|
||||
potentialBase64 == LargeFaultyBase64)
|
||||
{
|
||||
await this.webView2.EnsureCoreWebView2Async();
|
||||
}, DispatcherPriority.Render);
|
||||
|
||||
var curedSkillName = request.Skill.Name.Replace(" ", "_");
|
||||
var skillIconUrl = $"{BaseUrl}/{QueryUrl.Replace(NamePlaceholder, curedSkillName)}";
|
||||
this.logger.LogInformation($"Looking for icon at {skillIconUrl}");
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
this.webView2.Source = new Uri(skillIconUrl);
|
||||
});
|
||||
for(var i = 0; i < 5; i++)
|
||||
{
|
||||
if (this.cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.LogInformation("Executing extraction script");
|
||||
var responseTask = await Application.Current.Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
return await this.webView2.ExecuteScriptAsync(Scripts.GetHrefFromSkillPage);
|
||||
});
|
||||
var response = await responseTask;
|
||||
this.logger.LogInformation("Parsing response");
|
||||
var iconPayload = JsonConvert.DeserializeObject<IconPayload>(response);
|
||||
if (iconPayload is null)
|
||||
{
|
||||
this.logger.LogInformation("Bad response");
|
||||
await Task.Delay(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (iconPayload.SkillUrl != skillIconUrl.Replace("\"", "%22"))
|
||||
{
|
||||
this.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)
|
||||
{
|
||||
this.logger.LogInformation("Faulty base64 retrieved");
|
||||
continue;
|
||||
}
|
||||
|
||||
request.IconBase64 = potentialBase64;
|
||||
request.Finished = true;
|
||||
break;
|
||||
logger.LogInformation("Faulty base64 retrieved");
|
||||
await Task.Delay(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.logger.LogError($"Failed to retrieve icon for {request.Skill.Name}");
|
||||
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,113 +0,0 @@
|
||||
using Daybreak.Configuration;
|
||||
using Daybreak.Models.Builds;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Models;
|
||||
using Services.IconRetrieve;
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Core.Extensions;
|
||||
using System.Extensions;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Daybreak.Services.IconRetrieve
|
||||
{
|
||||
public sealed class IconRetriever : IIconRetriever
|
||||
{
|
||||
private const string NamePlaceholder = "[SKILLNAME]";
|
||||
private const string IconsDirectoryName = "Icons";
|
||||
private const string IconsLocation = $"{IconsDirectoryName}/{NamePlaceholder}.jpg";
|
||||
|
||||
private readonly IIconBrowser iconBrowser;
|
||||
private readonly ILogger<IconRetriever> logger;
|
||||
private readonly ILiveOptions<ApplicationConfiguration> liveOptions;
|
||||
|
||||
public IconRetriever(
|
||||
ILogger<IconRetriever> logger,
|
||||
IIconBrowser iconBrowser,
|
||||
ILiveOptions<ApplicationConfiguration> liveOptions)
|
||||
{
|
||||
this.iconBrowser = iconBrowser.ThrowIfNull();
|
||||
this.logger = logger.ThrowIfNull();
|
||||
this.liveOptions = liveOptions.ThrowIfNull();
|
||||
if (Directory.Exists(IconsDirectoryName) is false)
|
||||
{
|
||||
Directory.CreateDirectory(IconsDirectoryName);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Optional<Uri>> GetIconUri(Skill skill)
|
||||
{
|
||||
var maybeIconUri = this.GetLocalIcon(skill);
|
||||
if (maybeIconUri.ExtractValue() is Uri uri)
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
|
||||
if (this.liveOptions.Value.ExperimentalFeatures.DownloadIcons)
|
||||
{
|
||||
this.logger.LogInformation("Application is configured to download icons");
|
||||
return await this.DownloadIcon(skill);
|
||||
}
|
||||
|
||||
return Optional.None<Uri>();
|
||||
}
|
||||
|
||||
private async Task<Optional<Uri>> DownloadIcon(Skill skill)
|
||||
{
|
||||
var maybeBase64 = await this.GetBase64ForSkill(skill);
|
||||
if (maybeBase64.ExtractValue() is not string base64)
|
||||
{
|
||||
return Optional.None<Uri>();
|
||||
}
|
||||
|
||||
var bytes = Convert.FromBase64String(base64);
|
||||
var uri = await SaveIconLocally(skill, bytes);
|
||||
return new Uri(AppDomain.CurrentDomain.BaseDirectory + "/" + IconsLocation.Replace(NamePlaceholder, uri), UriKind.Absolute);
|
||||
}
|
||||
|
||||
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>();
|
||||
}
|
||||
|
||||
private async Task<Optional<string>> GetBase64ForSkill(Skill skill)
|
||||
{
|
||||
var request = new IconRequest { Skill = skill };
|
||||
this.iconBrowser.QueueIconRequest(request);
|
||||
|
||||
// Wait for the request to be served
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
if (request.Finished)
|
||||
{
|
||||
return request.IconBase64;
|
||||
}
|
||||
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
return Optional.None<string>();
|
||||
}
|
||||
|
||||
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,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;
|
||||
@@ -24,6 +24,7 @@ namespace Daybreak.Services.Updater
|
||||
{
|
||||
public sealed class ApplicationUpdater : IApplicationUpdater
|
||||
{
|
||||
private const string TemporaryInstallerFileName = "Daybreak.Installer.Temp.exe";
|
||||
private const string InstallerFileName = "Daybreak.Installer.exe";
|
||||
private const string UpdatedKey = "Updating";
|
||||
private const string RegistryKey = "Daybreak";
|
||||
@@ -177,6 +178,7 @@ namespace Daybreak.Services.Updater
|
||||
{
|
||||
if (UpdateMarkedInRegistry())
|
||||
{
|
||||
PerformPostUpdateActions();
|
||||
UnmarkUpdateInRegistry();
|
||||
}
|
||||
}
|
||||
@@ -257,5 +259,19 @@ namespace Daybreak.Services.Updater
|
||||
|
||||
return homeRegistryKey;
|
||||
}
|
||||
|
||||
private static void PerformPostUpdateActions()
|
||||
{
|
||||
RenameInstallerIfAvailable();
|
||||
}
|
||||
|
||||
private static void RenameInstallerIfAvailable()
|
||||
{
|
||||
if (File.Exists(TemporaryInstallerFileName))
|
||||
{
|
||||
File.Copy(TemporaryInstallerFileName, InstallerFileName, true);
|
||||
File.Delete(TemporaryInstallerFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Daybreak.Models;
|
||||
using Daybreak.Models.Progress;
|
||||
using Daybreak.Models.Versioning;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using Daybreak.Services.BuildTemplates;
|
||||
using Daybreak.Services.IconRetrieve;
|
||||
using Daybreak.Services.ViewManagement;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Services.IconRetrieve;
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Extensions;
|
||||
@@ -39,7 +38,7 @@ namespace Daybreak.Views
|
||||
public BuildTemplateView(
|
||||
IViewManager viewManager,
|
||||
IBuildTemplateManager buildTemplateManager,
|
||||
IIconRetriever iconRetriever,
|
||||
IIconCache iconRetriever,
|
||||
IIconBrowser iconBrowser,
|
||||
ILiveOptions<ApplicationConfiguration> liveOptions,
|
||||
ILogger<ChromiumBrowserWrapper> chromiumLogger,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ Param(
|
||||
Write-Output "Deleting pdb file"
|
||||
Remove-item .\Publish\Daybreak.pdb
|
||||
Remove-item .\Publish\Daybreak.Installer.pdb
|
||||
Move-Item -Path .\Publish\Daybreak.Installer.exe -Destination .\Publish\Daybreak.Installer.Temp.exe
|
||||
$zipPath = "Publish\daybreakv$version.zip"
|
||||
Write-Output "Compressing binaries to $zipPath"
|
||||
Compress-Archive .\Publish\* $zipPath -Force
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user