From 92a2195ccfde206b879fe74257be47625b623be1 Mon Sep 17 00:00:00 2001 From: erri120 Date: Fri, 21 Jan 2022 14:41:37 +0100 Subject: [PATCH 1/5] Update --- Wabbajack.App.Blazor/.editorconfig | 9 -- Wabbajack.App.Blazor/App.xaml.cs | 25 ++- .../Components/ModlistItem.razor | 28 ---- .../Components/ModlistItem.razor.scss | 82 ---------- Wabbajack.App.Blazor/Components/SideBar.razor | 1 + Wabbajack.App.Blazor/Components/TopBar.razor | 50 +++--- Wabbajack.App.Blazor/MainWindow.xaml.cs | 19 +-- Wabbajack.App.Blazor/Models/LoggerProvider.cs | 2 +- Wabbajack.App.Blazor/Pages/Configure.razor | 72 ++++----- Wabbajack.App.Blazor/Pages/Configure.razor.cs | 147 +++++++++--------- Wabbajack.App.Blazor/Pages/Create.razor | 7 +- Wabbajack.App.Blazor/Pages/Gallery.razor | 47 ++++-- Wabbajack.App.Blazor/Pages/Gallery.razor.cs | 91 ++++++----- Wabbajack.App.Blazor/Pages/Gallery.razor.scss | 87 ++++++++++- Wabbajack.App.Blazor/Pages/Install.razor | 6 +- Wabbajack.App.Blazor/Pages/Install.razor.cs | 8 +- Wabbajack.App.Blazor/Pages/Install.razor.scss | 2 +- Wabbajack.App.Blazor/Pages/Play.razor | 4 + Wabbajack.App.Blazor/Pages/Settings.razor | 6 +- Wabbajack.App.Blazor/Pages/Settings.razor.cs | 2 +- Wabbajack.App.Blazor/State/GlobalState.cs | 90 ----------- Wabbajack.App.Blazor/State/IStateContainer.cs | 28 ++++ Wabbajack.App.Blazor/State/InstallState.cs | 10 ++ Wabbajack.App.Blazor/State/StateContainer.cs | 81 ++++++++++ Wabbajack.App.Blazor/State/TaskBarState.cs | 4 +- Wabbajack.App.Blazor/Utility/Dialog.cs | 2 +- .../Utility/SystemParametersConstructor.cs | 24 +-- .../Wabbajack.App.Blazor.csproj | 1 + Wabbajack.App.Blazor/wwwroot/index.html | 3 +- Wabbajack.Common/CustomObservable.cs | 59 +++++++ 30 files changed, 548 insertions(+), 449 deletions(-) delete mode 100644 Wabbajack.App.Blazor/Components/ModlistItem.razor delete mode 100644 Wabbajack.App.Blazor/Components/ModlistItem.razor.scss delete mode 100644 Wabbajack.App.Blazor/State/GlobalState.cs create mode 100644 Wabbajack.App.Blazor/State/IStateContainer.cs create mode 100644 Wabbajack.App.Blazor/State/InstallState.cs create mode 100644 Wabbajack.App.Blazor/State/StateContainer.cs create mode 100644 Wabbajack.Common/CustomObservable.cs diff --git a/Wabbajack.App.Blazor/.editorconfig b/Wabbajack.App.Blazor/.editorconfig index 116e631a..fe9d005d 100644 --- a/Wabbajack.App.Blazor/.editorconfig +++ b/Wabbajack.App.Blazor/.editorconfig @@ -6,18 +6,9 @@ insert_final_newline = true # C# and Razor files [*.{cs,razor}] - -# CS8602: Dereference of a possibly null reference. -# CS8618: Non-nullable member is uninitialized. -# Reason: The compiler/IDE doesn't quite understand Dependency Injection yet. -dotnet_diagnostic.CS8602.severity = none -dotnet_diagnostic.CS8618.severity = none - # RZ10012: Markup element with unexpected name. # Reason: The component namespace is added to the global _Imports.razor file. dotnet_diagnostic.RZ10012.severity = none -dotnet_sort_system_directives_first = true - [*.scss] indent_size = 2 diff --git a/Wabbajack.App.Blazor/App.xaml.cs b/Wabbajack.App.Blazor/App.xaml.cs index 293cd9f4..66bbac5b 100644 --- a/Wabbajack.App.Blazor/App.xaml.cs +++ b/Wabbajack.App.Blazor/App.xaml.cs @@ -14,16 +14,17 @@ namespace Wabbajack.App.Blazor; public partial class App { private readonly IServiceProvider _serviceProvider; - private readonly IHost _host; public App() { - _host = Host.CreateDefaultBuilder(Array.Empty()) - .ConfigureLogging(c => { c.ClearProviders(); }) - .ConfigureServices((host, services) => { ConfigureServices(services); }) - .Build(); - - _serviceProvider = _host.Services; + _serviceProvider = Host.CreateDefaultBuilder(Array.Empty()) + .ConfigureLogging(loggingBuilder => + { + loggingBuilder.ClearProviders(); + }) + .ConfigureServices(services => ConfigureServices(services)) + .Build() + .Services; } private static IServiceCollection ConfigureServices(IServiceCollection services) @@ -33,24 +34,18 @@ public partial class App services.AddAllSingleton(); services.AddTransient(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(typeof(IStateContainer), typeof(StateContainer)); return services; } private void OnStartup(object sender, StartupEventArgs e) { var mainWindow = _serviceProvider.GetRequiredService(); - mainWindow!.Show(); + mainWindow.Show(); } private void OnExit(object sender, ExitEventArgs e) { Current.Shutdown(); - // using (_host) - // { - // _host.StopAsync(); - // } - // - // base.OnExit(e); } } diff --git a/Wabbajack.App.Blazor/Components/ModlistItem.razor b/Wabbajack.App.Blazor/Components/ModlistItem.razor deleted file mode 100644 index 4f05993a..00000000 --- a/Wabbajack.App.Blazor/Components/ModlistItem.razor +++ /dev/null @@ -1,28 +0,0 @@ -@using Wabbajack.DTOs - -@namespace Wabbajack.App.Blazor.Components - -
-
- @Metadata.Title -
- @ChildContent -
-
-
-
@Metadata.Title
-
@Metadata.Author
-
@Metadata.Description
-
-
-
- -@code { - - [Parameter] - public ModlistMetadata Metadata { get; set; } - - [Parameter] - public RenderFragment ChildContent { get; set; } - -} diff --git a/Wabbajack.App.Blazor/Components/ModlistItem.razor.scss b/Wabbajack.App.Blazor/Components/ModlistItem.razor.scss deleted file mode 100644 index 05024edb..00000000 --- a/Wabbajack.App.Blazor/Components/ModlistItem.razor.scss +++ /dev/null @@ -1,82 +0,0 @@ -@import "../Shared/Globals.scss"; - -$display-height: 225px; -$hover-icon-size: 75px; - -.item { - width: 400px; - height: 450px; - overflow: hidden; - margin: 0.5rem; - padding: 1rem; - background: rgba(255, 255, 255, 0.1); - box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); - backdrop-filter: blur(7px); - -webkit-backdrop-filter: blur(7px); - border: 1px solid rgba(255, 255, 255, 0.31); - - &:hover .display .image { - filter: blur(2px) brightness(70%); - } - - &:hover .display .interaction { - opacity: 1; - } - - .display { - position: relative; - height: $display-height; - display: flex; - justify-content: center; - align-items: center; - - .image { - position: absolute; - width: 100%; - height: 100%; - object-fit: contain; - transition: all 250ms ease-in-out; - } - - .interaction { - position: absolute; - opacity: 0; - transition: all 250ms ease-in-out; - - ::deep img { - width: $hover-icon-size; - height: $hover-icon-size; - margin: 0; - transition: all 150ms ease-in-out; - } - } - } - - .info { - padding-bottom: 1rem; - padding-left: 0.5rem; - padding-right: 0.5rem; - - .title { - color: white; - font-weight: 100; - font-size: 2rem; - line-height: 2.5rem; - margin: 0; - } - - .author { - color: lightgray; - font-size: 1rem; - } - - .description { - color: grey; - font-size: 0.9rem; - } - } - - .tags { - border-radius: 0.5rem; - } -} diff --git a/Wabbajack.App.Blazor/Components/SideBar.razor b/Wabbajack.App.Blazor/Components/SideBar.razor index a247e981..73bdb9ca 100644 --- a/Wabbajack.App.Blazor/Components/SideBar.razor +++ b/Wabbajack.App.Blazor/Components/SideBar.razor @@ -4,6 +4,7 @@ @* TODO: [Low] Replace logo with SVG? *@
+ @* TODO: wrap social icons in a button and make it clickable *@ diff --git a/Wabbajack.App.Blazor/Components/TopBar.razor b/Wabbajack.App.Blazor/Components/TopBar.razor index cf6f0767..9d359a8a 100644 --- a/Wabbajack.App.Blazor/Components/TopBar.razor +++ b/Wabbajack.App.Blazor/Components/TopBar.razor @@ -1,42 +1,36 @@ @using Wabbajack.App.Blazor.Pages @using Wabbajack.App.Blazor.Shared @using Wabbajack.App.Blazor.State +@inject NavigationManager _navigationManager +@inject IStateContainer _stateContainer @namespace Wabbajack.App.Blazor.Components -@* TODO: [Low] Clean this up a bit. *@
-
@code { - [Inject] - NavigationManager _navigationManager { get; set; } - - [Inject] - GlobalState GlobalState { get; set; } - - [CascadingParameter] - protected MainLayout _mainLayout { get; set; } + private static readonly Dictionary Pages = new() + { + {"Play", Play.Route}, + {"Gallery", Gallery.Route}, + {"Install", Install.Route}, + {"Create", Create.Route} + }; private void Navigate(string page) { @@ -45,14 +39,14 @@ protected override void OnInitialized() { - _navigationManager.LocationChanged += (o, args) => StateHasChanged(); - GlobalState.OnNavigationStateChange += StateHasChanged; + // TODO(erri120): update this + // _navigationManager.LocationChanged += (_, _) => StateHasChanged(); + // _globalState.OnNavigationStateChange += StateHasChanged; } private string CurrentPage(string page) { - string relativePath = _navigationManager.ToBaseRelativePath(_navigationManager.Uri).ToLower(); - return page.ToLower() == relativePath ? "active" : ""; + var relativePath = _navigationManager.ToBaseRelativePath(_navigationManager.Uri); + return page.Equals(relativePath, StringComparison.OrdinalIgnoreCase) ? "active" : string.Empty; } - } diff --git a/Wabbajack.App.Blazor/MainWindow.xaml.cs b/Wabbajack.App.Blazor/MainWindow.xaml.cs index 4640cf57..782b736a 100644 --- a/Wabbajack.App.Blazor/MainWindow.xaml.cs +++ b/Wabbajack.App.Blazor/MainWindow.xaml.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.AspNetCore.Components; using Microsoft.Extensions.Logging; using Wabbajack.App.Blazor.Models; using Wabbajack.App.Blazor.State; @@ -11,20 +12,20 @@ namespace Wabbajack.App.Blazor; public partial class MainWindow { - private readonly ILogger _logger; - private readonly LoggerProvider _loggerProvider; + private readonly ILogger _logger; + private readonly LoggerProvider _loggerProvider; private readonly SystemParametersConstructor _systemParams; - private readonly GlobalState _globalState; + private readonly IStateContainer _stateContainer; public MainWindow(ILogger logger, IServiceProvider serviceProvider, LoggerProvider loggerProvider, - SystemParametersConstructor systemParams, GlobalState globalState) + SystemParametersConstructor systemParams, IStateContainer stateContainer) { - _logger = logger; + _logger = logger; _loggerProvider = loggerProvider; - _systemParams = systemParams; - _globalState = globalState; + _systemParams = systemParams; + _stateContainer = stateContainer; - _globalState.OnTaskBarStateChange += state => + _stateContainer.TaskBarStateObservable.Subscribe(state => { Dispatcher.InvokeAsync(() => { @@ -32,7 +33,7 @@ public partial class MainWindow TaskBarItem.ProgressState = state.State; TaskBarItem.ProgressValue = state.ProgressValue; }); - }; + }); InitializeComponent(); BlazorWebView.Services = serviceProvider; diff --git a/Wabbajack.App.Blazor/Models/LoggerProvider.cs b/Wabbajack.App.Blazor/Models/LoggerProvider.cs index e5c9168c..af1ddc52 100644 --- a/Wabbajack.App.Blazor/Models/LoggerProvider.cs +++ b/Wabbajack.App.Blazor/Models/LoggerProvider.cs @@ -71,7 +71,7 @@ public class LoggerProvider : ILoggerProvider private void LogToFile(ILogMessage logMessage) { - string? line = $"[{logMessage.TimeStamp - _startupTime}] {logMessage.LongMessage}"; + var line = $"[{logMessage.TimeStamp - _startupTime}] {logMessage.LongMessage}"; lock (_logStream) { _logStream.Write(line); diff --git a/Wabbajack.App.Blazor/Pages/Configure.razor b/Wabbajack.App.Blazor/Pages/Configure.razor index 96b5d624..fc114df9 100644 --- a/Wabbajack.App.Blazor/Pages/Configure.razor +++ b/Wabbajack.App.Blazor/Pages/Configure.razor @@ -1,52 +1,50 @@ -@page "/Configure" - +@page "/configure" @using Wabbajack.App.Blazor.State @namespace Wabbajack.App.Blazor.Pages
- +
- @* TODO: [High] Find a cleaner way to show/hide components based on state. *@ - @* TODO: [Low] Split each "side" into their own components? *@ -
- @if (!string.IsNullOrEmpty(ModList.Name)) - { - if (InstallState != GlobalState.InstallStateEnum.Installing) + @if (Modlist is not null) + { +
+ @if (InstallState != InstallState.Installing) { - + } - else if (InstallState == GlobalState.InstallStateEnum.Installing) + else { - - // TODO: [Low] Step logging. + + // TODO: [Low] Step logging } - } -
-
- @if (!string.IsNullOrEmpty(Image)) - { - if (InstallState != GlobalState.InstallStateEnum.Installing) - { - - } - else if (InstallState == GlobalState.InstallStateEnum.Installing) - { - // TODO: [Low] Implement featured mod slideshow. - - } - } -
+
+
+ @* TODO: whatever this is *@ + @* @if (!string.IsNullOrEmpty(Image)) *@ + @* { *@ + @* if (InstallState != GlobalState.InstallStateEnum.Installing) *@ + @* { *@ + @* *@ + @* } *@ + @* else if (InstallState == GlobalState.InstallStateEnum.Installing) *@ + @* { *@ + @* // TODO: [Low] Implement featured mod slideshow. *@ + @* *@ + @* } *@ + @* } *@ +
+ }
- @if (InstallState == GlobalState.InstallStateEnum.Installing) + @if (InstallState == InstallState.Installing) {
- +
} - @if (InstallState != GlobalState.InstallStateEnum.Installing) + else {
@@ -57,9 +55,9 @@ Download Location
- @ModListPath - @InstallPath - @DownloadPath + @ModlistPath.ToString() + @InstallPath.ToString() + @DownloadPath.ToString()
@@ -75,3 +73,7 @@
}
+ +@code { + public const string Route = "/configure"; +} diff --git a/Wabbajack.App.Blazor/Pages/Configure.razor.cs b/Wabbajack.App.Blazor/Pages/Configure.razor.cs index 535a2b76..ac649895 100644 --- a/Wabbajack.App.Blazor/Pages/Configure.razor.cs +++ b/Wabbajack.App.Blazor/Pages/Configure.razor.cs @@ -1,6 +1,4 @@ using System; -using System.Diagnostics; -using System.IO; using System.Threading; using Microsoft.AspNetCore.Components; using Wabbajack.DTOs; @@ -12,6 +10,8 @@ using Wabbajack.Downloaders.GameFile; using Wabbajack.Hashing.xxHash64; using Wabbajack.Services.OSIntegrated; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.JSInterop; using Wabbajack.App.Blazor.Models; using Wabbajack.App.Blazor.State; @@ -19,71 +19,74 @@ namespace Wabbajack.App.Blazor.Pages; public partial class Configure { - [Inject] private NavigationManager NavigationManager { get; set; } - [Inject] private GlobalState GlobalState { get; set; } - [Inject] private DTOSerializer _dtos { get; set; } - [Inject] private IServiceProvider _serviceProvider { get; set; } - [Inject] private SystemParametersConstructor _parametersConstructor { get; set; } - [Inject] private IGameLocator _gameLocator { get; set; } - [Inject] private SettingsManager _settingsManager { get; set; } - [Inject] private LoggerProvider _loggerProvider { get; set; } + [Inject] private ILogger Logger { get; set; } = default!; + [Inject] private IStateContainer StateContainer { get; set; } = default!; + [Inject] private DTOSerializer DTOs { get; set; } = default!; + [Inject] private IServiceProvider ServiceProvider { get; set; } = default!; + [Inject] private SystemParametersConstructor ParametersConstructor { get; set; } = default!; + [Inject] private IGameLocator GameLocator { get; set; } = default!; + [Inject] private SettingsManager SettingsManager { get; set; } = default!; + [Inject] private LoggerProvider LoggerProvider { get; set; } = default!; + [Inject] private JSRuntime JSRuntime { get; set; } = default!; + + private ModList? Modlist => StateContainer.Modlist; - private string Image { get; set; } - private ModList ModList { get; set; } = new(); // Init a new modlist so we can listen for changes in Blazor components. - private AbsolutePath ModListPath { get; set; } - private AbsolutePath InstallPath { get; set; } + private AbsolutePath ModlistPath => StateContainer.ModlistPath; + private AbsolutePath InstallPath { get; set; } private AbsolutePath DownloadPath { get; set; } - private string StatusText { get; set; } - public GlobalState.InstallStateEnum InstallState { get; set; } - private LoggerProvider.ILogMessage CurrentLog { get; set; } + private string StatusText { get; set; } = string.Empty; + private InstallState InstallState => StateContainer.InstallState; + // private LoggerProvider.ILogMessage CurrentLog { get; set; } private const string InstallSettingsPrefix = "install-settings-"; + private bool _shouldRender; + protected override bool ShouldRender() => _shouldRender; + protected override async Task OnInitializedAsync() { // var Location = KnownFolders.EntryPoint.Combine("downloaded_mod_lists", machineURL).WithExtension(Ext.Wabbajack); - GlobalState.OnInstallStateChange += () => InstallState = GlobalState.InstallState; + await CheckValidInstallPath(); - await base.OnInitializedAsync(); + _shouldRender = true; } private async Task CheckValidInstallPath() { - if (GlobalState.ModListPath == AbsolutePath.Empty) return; + if (ModlistPath == AbsolutePath.Empty) return; + + var modlist = await StandardInstaller.LoadFromFile(DTOs, ModlistPath); + StateContainer.Modlist = modlist; - ModListPath = GlobalState.ModListPath; - ModList = await StandardInstaller.LoadFromFile(_dtos, ModListPath); - GlobalState.ModList = ModList; + var hex = (await ModlistPath.ToString().Hash()).ToHex(); + var prevSettings = await SettingsManager.Load(InstallSettingsPrefix + hex); - string hex = (await ModListPath.ToString().Hash()).ToHex(); - var prevSettings = await _settingsManager.Load(InstallSettingsPrefix + hex); - - if (prevSettings.ModListLocation == ModListPath) + if (prevSettings.ModlistLocation == ModlistPath) { - ModListPath = prevSettings.ModListLocation; + StateContainer.ModlistPath = prevSettings.ModlistLocation; InstallPath = prevSettings.InstallLocation; - DownloadPath = prevSettings.DownloadLoadction; + DownloadPath = prevSettings.DownloadLocation; //ModlistMetadata = metadata ?? prevSettings.Metadata; } - Stream image = await StandardInstaller.ModListImageStream(ModListPath); - await using var reader = new MemoryStream(); - await image.CopyToAsync(reader); - Image = $"data:image/png;base64,{Convert.ToBase64String(reader.ToArray())}"; + // see https://docs.microsoft.com/en-us/aspnet/core/blazor/images?view=aspnetcore-6.0#streaming-examples + var imageStream = await StandardInstaller.ModListImageStream(ModlistPath); + var dotnetImageStream = new DotNetStreamReference(imageStream); + // setImageUsingStreaming accepts the img id and the data stream + await JSRuntime.InvokeVoidAsync("setImageUsingStreaming", "background-image", dotnetImageStream); } private async void SelectInstallFolder() { try { - AbsolutePath? thing = await Dialog.ShowDialogNonBlocking(true); - if (thing != null) InstallPath = (AbsolutePath)thing; - StateHasChanged(); + var installPath = await Dialog.ShowDialogNonBlocking(true); + if (installPath is not null) InstallPath = (AbsolutePath)installPath; } - catch (Exception ex) + catch (Exception e) { - Debug.Print(ex.Message); + Logger.LogError(e, "Exception selecting install folder"); } } @@ -91,68 +94,68 @@ public partial class Configure { try { - AbsolutePath? thing = await Dialog.ShowDialogNonBlocking(true); - if (thing != null) DownloadPath = (AbsolutePath)thing; - StateHasChanged(); + var downloadPath = await Dialog.ShowDialogNonBlocking(true); + if (downloadPath is not null) DownloadPath = (AbsolutePath)downloadPath; } - catch (Exception ex) + catch (Exception e) { - Debug.Print(ex.Message); + Logger.LogError(e, "Exception selecting download folder"); } } private async Task Install() { - GlobalState.InstallState = GlobalState.InstallStateEnum.Installing; - await Task.Run(BeginInstall); + if (Modlist is null) return; + + StateContainer.InstallState = InstallState.Installing; + await Task.Run(() => BeginInstall(Modlist)); } - private async Task BeginInstall() + private async Task BeginInstall(ModList modlist) { - string postfix = (await ModListPath.ToString().Hash()).ToHex(); - await _settingsManager.Save(InstallSettingsPrefix + postfix, new SavedInstallSettings + var postfix = (await ModlistPath.ToString().Hash()).ToHex(); + await SettingsManager.Save(InstallSettingsPrefix + postfix, new SavedInstallSettings { - ModListLocation = ModListPath, - InstallLocation = InstallPath, - DownloadLoadction = DownloadPath + ModlistLocation = ModlistPath, + InstallLocation = InstallPath, + DownloadLocation = DownloadPath }); try { - var installer = StandardInstaller.Create(_serviceProvider, new InstallerConfiguration + var installer = StandardInstaller.Create(ServiceProvider, new InstallerConfiguration { - Game = ModList.GameType, - Downloads = DownloadPath, - Install = InstallPath, - ModList = ModList, - ModlistArchive = ModListPath, - SystemParameters = _parametersConstructor.Create(), - GameFolder = _gameLocator.GameLocation(ModList.GameType) + Game = modlist.GameType, + Downloads = DownloadPath, + Install = InstallPath, + ModList = modlist, + ModlistArchive = ModlistPath, + SystemParameters = ParametersConstructor.Create(), + GameFolder = GameLocator.GameLocation(modlist.GameType) }); - - + installer.OnStatusUpdate = update => { - if (StatusText != update.StatusText) - { - StatusText = update.StatusText; - InvokeAsync(StateHasChanged); - } + var (statusText, _, _) = update; + if (StatusText == statusText) return; + StatusText = statusText; }; await installer.Begin(CancellationToken.None); + StateContainer.InstallState = InstallState.Success; } - catch (Exception ex) + catch (Exception e) { - Debug.Print(ex.Message); + Logger.LogError(e, "Exception installing Modlist"); + StateContainer.InstallState = InstallState.Failure; } } } internal class SavedInstallSettings { - public AbsolutePath ModListLocation { get; set; } - public AbsolutePath InstallLocation { get; set; } - public AbsolutePath DownloadLoadction { get; set; } - public ModlistMetadata Metadata { get; set; } + public AbsolutePath ModlistLocation { get; set; } = AbsolutePath.Empty; + public AbsolutePath InstallLocation { get; set; } = AbsolutePath.Empty; + public AbsolutePath DownloadLocation { get; set; } = AbsolutePath.Empty; + // public ModlistMetadata Metadata { get; set; } } diff --git a/Wabbajack.App.Blazor/Pages/Create.razor b/Wabbajack.App.Blazor/Pages/Create.razor index a21fb689..2dadf28b 100644 --- a/Wabbajack.App.Blazor/Pages/Create.razor +++ b/Wabbajack.App.Blazor/Pages/Create.razor @@ -1,4 +1,4 @@ -@page "/Create" +@page "/create" @namespace Wabbajack.App.Blazor.Pages
@@ -6,3 +6,8 @@
+ +@code +{ + public const string Route = "/create"; +} diff --git a/Wabbajack.App.Blazor/Pages/Gallery.razor b/Wabbajack.App.Blazor/Pages/Gallery.razor index b8c2d185..6c8c42eb 100644 --- a/Wabbajack.App.Blazor/Pages/Gallery.razor +++ b/Wabbajack.App.Blazor/Pages/Gallery.razor @@ -1,24 +1,51 @@ -@page "/Gallery" +@page "/gallery" -@using Wabbajack.DTOs @using Wabbajack.RateLimiter +@using System.Globalization @namespace Wabbajack.App.Blazor.Pages
- @foreach (ModlistMetadata modlist in _listItems) + @if (_errorLoadingModlists) { - - - - + @* TODO: error *@ } - @if (DownloadProgress != Percent.Zero) + else if (!Modlists.Any()) { - + @* TODO: loading *@ + } + else + { + @foreach (var modlist in Modlists) + { +
+
+ @modlist.Title +
+ + +
+
+
+
@modlist.Title
+
@modlist.Author
+
@modlist.Description
+
+
+
+ } + } + + @if (_downloadProgress != Percent.Zero && _downloadingMetaData is not null) + { +
- +
}
+ +@code { + public const string Route = "/gallery"; +} diff --git a/Wabbajack.App.Blazor/Pages/Gallery.razor.cs b/Wabbajack.App.Blazor/Pages/Gallery.razor.cs index 3802bcd6..b1d911b0 100644 --- a/Wabbajack.App.Blazor/Pages/Gallery.razor.cs +++ b/Wabbajack.App.Blazor/Pages/Gallery.razor.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Shell; @@ -20,33 +21,35 @@ namespace Wabbajack.App.Blazor.Pages; public partial class Gallery { - [Inject] private GlobalState GlobalState { get; set; } - [Inject] private NavigationManager NavigationManager { get; set; } - [Inject] private ILogger _logger { get; set; } - [Inject] private Client _client { get; set; } - [Inject] private ModListDownloadMaintainer _maintainer { get; set; } + [Inject] private ILogger Logger { get; set; } = default!; + [Inject] private IStateContainer StateContainer { get; set; } = default!; + [Inject] private NavigationManager NavigationManager { get; set; } = default!; + [Inject] private ModListDownloadMaintainer Maintainer { get; set; } = default!; - public Percent DownloadProgress { get; set; } = Percent.Zero; - public ModlistMetadata DownloadingMetaData { get; set; } = new ModlistMetadata(); + private Percent _downloadProgress = Percent.Zero; + private ModlistMetadata? _downloadingMetaData; - private List _listItems { get; set; } = new(); + private IEnumerable Modlists => StateContainer.Modlists; + + private bool _errorLoadingModlists; + + private bool _shouldRender; + protected override bool ShouldRender() => _shouldRender; protected override async Task OnInitializedAsync() { - try + if (!StateContainer.Modlists.Any()) { - _logger.LogInformation("Getting modlists..."); - ModlistMetadata[] modLists = await _client.LoadLists(); - _listItems.AddRange(modLists.ToList()); - StateHasChanged(); + var res = await StateContainer.LoadModlistMetadata(); + if (!res) + { + _errorLoadingModlists = true; + _shouldRender = true; + return; + } } - catch (Exception ex) - { - //TODO: [Critical] Figure out why an exception is thrown on first navigation. - _logger.LogError(ex, "Error while loading lists"); - } - - await base.OnInitializedAsync(); + + _shouldRender = true; } private async void OnClickDownload(ModlistMetadata metadata) @@ -62,38 +65,42 @@ public partial class Gallery private async Task Download(ModlistMetadata metadata) { - GlobalState.NavigationAllowed = false; - DownloadingMetaData = metadata; - await using Timer timer = new(_ => InvokeAsync(StateHasChanged)); - timer.Change(TimeSpan.FromMilliseconds(250), TimeSpan.FromMilliseconds(250)); + StateContainer.NavigationAllowed = false; + _downloadingMetaData = metadata; + try { - (IObservable progress, Task task) = _maintainer.DownloadModlist(metadata); + var (progress, task) = Maintainer.DownloadModlist(metadata); - GlobalState.SetTaskBarState(TaskbarItemProgressState.Indeterminate,$"Downloading {metadata.Title}"); - - var dispose = progress.Subscribe(p => + var dispose = progress + .DistinctUntilChanged(p => p.Value) + .Throttle(TimeSpan.FromMilliseconds(100)) + .Subscribe(p => { - DownloadProgress = p; - GlobalState.SetTaskBarState(TaskbarItemProgressState.Indeterminate,$"Downloading {metadata.Title}", p.Value); - }); + _downloadProgress = p; + StateContainer.TaskBarState = new TaskBarState + { + Description = $"Downloading {metadata.Title}", + State = TaskbarItemProgressState.Indeterminate, + ProgressValue = p.Value + }; + }, () => { StateContainer.TaskBarState = new TaskBarState(); }); await task; - //await _wjClient.SendMetric("downloading", Metadata.Title); dispose.Dispose(); - GlobalState.SetTaskBarState(); - - - AbsolutePath path = KnownFolders.EntryPoint.Combine("downloaded_mod_lists", metadata.Links.MachineURL).WithExtension(Ext.Wabbajack); - GlobalState.ModListPath = path; - NavigationManager.NavigateTo("/Configure"); + + var path = KnownFolders.EntryPoint.Combine("downloaded_mod_lists", metadata.Links.MachineURL).WithExtension(Ext.Wabbajack); + StateContainer.ModlistPath = path; + NavigationManager.NavigateTo(Configure.Route); } catch (Exception e) { - Debug.Print(e.Message); + Logger.LogError(e, "Exception downloading Modlist {Name}", metadata.Title); + } + finally + { + StateContainer.TaskBarState = new TaskBarState(); + StateContainer.NavigationAllowed = true; } - - await timer.DisposeAsync(); - GlobalState.NavigationAllowed = true; } } diff --git a/Wabbajack.App.Blazor/Pages/Gallery.razor.scss b/Wabbajack.App.Blazor/Pages/Gallery.razor.scss index 3b396446..3d982d7b 100644 --- a/Wabbajack.App.Blazor/Pages/Gallery.razor.scss +++ b/Wabbajack.App.Blazor/Pages/Gallery.razor.scss @@ -1,6 +1,89 @@ -#content { +@import "../Shared/Globals.scss"; + +#content { width: 100%; display: flex; flex-flow: row wrap; justify-content: space-evenly; -} \ No newline at end of file +} + +$display-height: 225px; +$hover-icon-size: 75px; + +.item { + width: 400px; + height: 450px; + overflow: hidden; + margin: 0.5rem; + padding: 1rem; + background: rgba(255, 255, 255, 0.1); + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(7px); + -webkit-backdrop-filter: blur(7px); + border: 1px solid rgba(255, 255, 255, 0.31); + + &:hover .display .image { + filter: blur(2px) brightness(70%); + } + + &:hover .display .interaction { + opacity: 1; + } + + .display { + position: relative; + height: $display-height; + display: flex; + justify-content: center; + align-items: center; + + .image { + position: absolute; + width: 100%; + height: 100%; + object-fit: contain; + transition: all 250ms ease-in-out; + } + + .interaction { + position: absolute; + opacity: 0; + transition: all 250ms ease-in-out; + + ::deep img { + width: $hover-icon-size; + height: $hover-icon-size; + margin: 0; + transition: all 150ms ease-in-out; + } + } + } + + .info { + padding-bottom: 1rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + + .title { + color: white; + font-weight: 100; + font-size: 2rem; + line-height: 2.5rem; + margin: 0; + } + + .author { + color: lightgray; + font-size: 1rem; + } + + .description { + color: grey; + font-size: 0.9rem; + } + } + + .tags { + border-radius: 0.5rem; + } +} diff --git a/Wabbajack.App.Blazor/Pages/Install.razor b/Wabbajack.App.Blazor/Pages/Install.razor index 48b76af0..43372808 100644 --- a/Wabbajack.App.Blazor/Pages/Install.razor +++ b/Wabbajack.App.Blazor/Pages/Install.razor @@ -1,4 +1,4 @@ -@page "/Install" +@page "/install" @namespace Wabbajack.App.Blazor.Pages @@ -24,3 +24,7 @@ + +@code { + public const string Route = "/install"; +} diff --git a/Wabbajack.App.Blazor/Pages/Install.razor.cs b/Wabbajack.App.Blazor/Pages/Install.razor.cs index a30bf030..a9df94f9 100644 --- a/Wabbajack.App.Blazor/Pages/Install.razor.cs +++ b/Wabbajack.App.Blazor/Pages/Install.razor.cs @@ -8,8 +8,8 @@ namespace Wabbajack.App.Blazor.Pages; public partial class Install { - [Inject] private NavigationManager NavigationManager { get; set; } - [Inject] private GlobalState GlobalState { get; set; } + [Inject] private NavigationManager NavigationManager { get; set; } = default!; + [Inject] private IStateContainer StateContainer { get; set; } = default!; private void SelectFile() { @@ -18,10 +18,10 @@ public partial class Install dialog.Multiselect = false; dialog.Filters.Add(new CommonFileDialogFilter("Wabbajack File", "*" + Ext.Wabbajack)); if (dialog.ShowDialog() != CommonFileDialogResult.Ok) return; - GlobalState.ModListPath = dialog.FileName.ToAbsolutePath(); + StateContainer.ModlistPath = dialog.FileName.ToAbsolutePath(); } - NavigationManager.NavigateTo("/Configure"); + NavigationManager.NavigateTo(Configure.Route); } private void VerifyFile(AbsolutePath path) { } diff --git a/Wabbajack.App.Blazor/Pages/Install.razor.scss b/Wabbajack.App.Blazor/Pages/Install.razor.scss index b1c562ef..afea956a 100644 --- a/Wabbajack.App.Blazor/Pages/Install.razor.scss +++ b/Wabbajack.App.Blazor/Pages/Install.razor.scss @@ -46,4 +46,4 @@ } } } -} \ No newline at end of file +} diff --git a/Wabbajack.App.Blazor/Pages/Play.razor b/Wabbajack.App.Blazor/Pages/Play.razor index 88bd1068..6f3d6896 100644 --- a/Wabbajack.App.Blazor/Pages/Play.razor +++ b/Wabbajack.App.Blazor/Pages/Play.razor @@ -21,3 +21,7 @@ // } // } } + +@code { + public const string Route = "/"; +} diff --git a/Wabbajack.App.Blazor/Pages/Settings.razor b/Wabbajack.App.Blazor/Pages/Settings.razor index c67d20d6..f2dc52c9 100644 --- a/Wabbajack.App.Blazor/Pages/Settings.razor +++ b/Wabbajack.App.Blazor/Pages/Settings.razor @@ -1,4 +1,4 @@ -@page "/Settings" +@page "/settings" @namespace Wabbajack.App.Blazor.Pages
@@ -6,3 +6,7 @@
+ +@code { + public const string Route = "/settings"; +} diff --git a/Wabbajack.App.Blazor/Pages/Settings.razor.cs b/Wabbajack.App.Blazor/Pages/Settings.razor.cs index f702e65c..6348712c 100644 --- a/Wabbajack.App.Blazor/Pages/Settings.razor.cs +++ b/Wabbajack.App.Blazor/Pages/Settings.razor.cs @@ -13,7 +13,7 @@ public partial class Settings { try { - ResourceSettingsManager.ResourceSetting resource = await _resourceSettingsManager.GetSettings("Downloads"); + var resource = await _resourceSettingsManager.GetSettings("Downloads"); StateHasChanged(); } catch (Exception ex) { } diff --git a/Wabbajack.App.Blazor/State/GlobalState.cs b/Wabbajack.App.Blazor/State/GlobalState.cs deleted file mode 100644 index f90985f1..00000000 --- a/Wabbajack.App.Blazor/State/GlobalState.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Windows.Shell; -using Wabbajack.DTOs; -using Wabbajack.Paths; - -namespace Wabbajack.App.Blazor.State; - -public class GlobalState -{ - #region Navigation Allowed - - private bool _navigationAllowed = true; - - public event Action OnNavigationStateChange; - - public bool NavigationAllowed - { - get => _navigationAllowed; - set - { - _navigationAllowed = value; - OnNavigationStateChange?.Invoke(); - } - } - - #endregion - - #region Install - - private InstallStateEnum _installState; - private AbsolutePath _modListPath; - private ModList _modList; - - public event Action OnModListPathChange; - public event Action OnModListChange; - public event Action OnInstallStateChange; - - public event Action OnTaskBarStateChange; - - public void SetTaskBarState(TaskbarItemProgressState state = TaskbarItemProgressState.None, string description="", double progress = 0) - { - OnTaskBarStateChange?.Invoke(new TaskBarState - { - State = state, - ProgressValue = progress, - Description = description - }); - } - - public AbsolutePath ModListPath - { - get => _modListPath; - set - { - _modListPath = value; - OnModListPathChange?.Invoke(); - } - } - - public ModList ModList - { - get => _modList; - set - { - _modList = value; - OnModListChange?.Invoke(); - } - } - - public InstallStateEnum InstallState - { - get => _installState; - set - { - _installState = value; - OnInstallStateChange?.Invoke(); - } - } - - public enum InstallStateEnum - { - Waiting, - Configuration, - Installing, - Success, - Failure - } - - #endregion -} diff --git a/Wabbajack.App.Blazor/State/IStateContainer.cs b/Wabbajack.App.Blazor/State/IStateContainer.cs new file mode 100644 index 00000000..b4de0b8e --- /dev/null +++ b/Wabbajack.App.Blazor/State/IStateContainer.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Wabbajack.DTOs; +using Wabbajack.Paths; + +namespace Wabbajack.App.Blazor.State; + +public interface IStateContainer +{ + IEnumerable Modlists { get; } + Task LoadModlistMetadata(); + + IObservable NavigationAllowedObservable { get; } + bool NavigationAllowed { get; set; } + + IObservable ModlistPathObservable { get; } + AbsolutePath ModlistPath { get; set; } + + IObservable ModlistObservable { get; } + ModList? Modlist { get; set; } + + IObservable InstallStateObservable { get; } + InstallState InstallState { get; set; } + + IObservable TaskBarStateObservable { get; } + TaskBarState TaskBarState { get; set; } +} diff --git a/Wabbajack.App.Blazor/State/InstallState.cs b/Wabbajack.App.Blazor/State/InstallState.cs new file mode 100644 index 00000000..59e1077e --- /dev/null +++ b/Wabbajack.App.Blazor/State/InstallState.cs @@ -0,0 +1,10 @@ +namespace Wabbajack.App.Blazor.State; + +public enum InstallState +{ + Waiting, + Configuration, + Installing, + Success, + Failure +} diff --git a/Wabbajack.App.Blazor/State/StateContainer.cs b/Wabbajack.App.Blazor/State/StateContainer.cs new file mode 100644 index 00000000..a45aa67f --- /dev/null +++ b/Wabbajack.App.Blazor/State/StateContainer.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Wabbajack.Common; +using Wabbajack.DTOs; +using Wabbajack.Networking.WabbajackClientApi; +using Wabbajack.Paths; + +namespace Wabbajack.App.Blazor.State; + +public class StateContainer : IStateContainer +{ + private readonly ILogger _logger; + private readonly Client _client; + + public StateContainer(ILogger logger, Client client) + { + _logger = logger; + _client = client; + } + + private ModlistMetadata[] _modlists = Array.Empty(); + public IEnumerable Modlists => _modlists; + + public async Task LoadModlistMetadata() + { + try + { + var lists = await _client.LoadLists(); + _modlists = lists; + return _modlists.Any(); + } + catch (Exception e) + { + _logger.LogError(e, "Exception loading Modlists"); + return false; + } + } + + private readonly CustomObservable _navigationAllowedObservable = new(true); + public IObservable NavigationAllowedObservable => _navigationAllowedObservable; + public bool NavigationAllowed + { + get => _navigationAllowedObservable.Value; + set => _navigationAllowedObservable.Value = value; + } + + private readonly CustomObservable _modlistPathObservable = new(AbsolutePath.Empty); + public IObservable ModlistPathObservable => _modlistPathObservable; + public AbsolutePath ModlistPath + { + get => _modlistPathObservable.Value; + set => _modlistPathObservable.Value = value; + } + + private readonly CustomObservable _modlistObservable = new(null); + public IObservable ModlistObservable => _modlistObservable; + public ModList? Modlist + { + get => _modlistObservable.Value; + set => _modlistObservable.Value = value; + } + + private readonly CustomObservable _installStateObservable = new(InstallState.Waiting); + public IObservable InstallStateObservable => _installStateObservable; + public InstallState InstallState + { + get => _installStateObservable.Value; + set => _installStateObservable.Value = value; + } + + private readonly CustomObservable _taskBarStateObservable = new(new TaskBarState()); + public IObservable TaskBarStateObservable => _taskBarStateObservable; + public TaskBarState TaskBarState + { + get => _taskBarStateObservable.Value; + set => _taskBarStateObservable.Value = value; + } +} diff --git a/Wabbajack.App.Blazor/State/TaskBarState.cs b/Wabbajack.App.Blazor/State/TaskBarState.cs index 02cc4ffb..560110a1 100644 --- a/Wabbajack.App.Blazor/State/TaskBarState.cs +++ b/Wabbajack.App.Blazor/State/TaskBarState.cs @@ -4,7 +4,7 @@ namespace Wabbajack.App.Blazor.State; public class TaskBarState { - public string Description { get; set; } + public string Description { get; set; } = string.Empty; public double ProgressValue { get; set; } - public TaskbarItemProgressState State { get; set; } + public TaskbarItemProgressState State { get; set; } = TaskbarItemProgressState.None; } diff --git a/Wabbajack.App.Blazor/Utility/Dialog.cs b/Wabbajack.App.Blazor/Utility/Dialog.cs index 5a4596fc..d333473f 100644 --- a/Wabbajack.App.Blazor/Utility/Dialog.cs +++ b/Wabbajack.App.Blazor/Utility/Dialog.cs @@ -20,7 +20,7 @@ public static class Dialog var dialog = new CommonOpenFileDialog(); dialog.IsFolderPicker = isFolderPicker; dialog.Multiselect = false; - CommonFileDialogResult result = dialog.ShowDialog(newWindow); + var result = dialog.ShowDialog(newWindow); return result == CommonFileDialogResult.Ok ? dialog.FileName : null; }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()) .ContinueWith(result => result.Result?.ToAbsolutePath()) diff --git a/Wabbajack.App.Blazor/Utility/SystemParametersConstructor.cs b/Wabbajack.App.Blazor/Utility/SystemParametersConstructor.cs index 172da1c7..ca33aedb 100644 --- a/Wabbajack.App.Blazor/Utility/SystemParametersConstructor.cs +++ b/Wabbajack.App.Blazor/Utility/SystemParametersConstructor.cs @@ -40,14 +40,14 @@ public class SystemParametersConstructor SetProcessDPIAware(); unsafe { - List<(int Width, int Height, bool IsPrimary)>? col = new List<(int Width, int Height, bool IsPrimary)>(); + var col = new List<(int Width, int Height, bool IsPrimary)>(); EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, delegate(IntPtr hMonitor, IntPtr hdcMonitor, RECT* lprcMonitor, void* dwData) { var mi = new MONITORINFOEX(); mi.cbSize = Marshal.SizeOf(mi); - bool success = GetMonitorInfo(hMonitor, (MONITORINFO*)&mi); + var success = GetMonitorInfo(hMonitor, (MONITORINFO*)&mi); if (success) col.Add((mi.Monitor.right - mi.Monitor.left, mi.Monitor.bottom - mi.Monitor.top, mi.Flags == MONITORINFO_Flags.MONITORINFOF_PRIMARY)); @@ -60,17 +60,17 @@ public class SystemParametersConstructor public SystemParameters Create() { - (int width, int height, _) = GetDisplays().First(d => d.IsPrimary); + (var width, var height, _) = GetDisplays().First(d => d.IsPrimary); /*using var f = new SharpDX.DXGI.Factory1(); var video_memory = f.Adapters1.Select(a => Math.Max(a.Description.DedicatedSystemMemory, (long)a.Description.DedicatedVideoMemory)).Max();*/ - ulong dxgiMemory = 0UL; + var dxgiMemory = 0UL; unsafe { - using DXGI? api = DXGI.GetApi(); + using var api = DXGI.GetApi(); IDXGIFactory1* factory1 = default; @@ -79,15 +79,15 @@ public class SystemParametersConstructor //https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-createdxgifactory1 SilkMarshal.ThrowHResult(api.CreateDXGIFactory1(SilkMarshal.GuidPtrOf(), (void**)&factory1)); - uint i = 0u; + var i = 0u; while (true) { IDXGIAdapter1* adapter1 = default; //https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgifactory1-enumadapters1 - int res = factory1->EnumAdapters1(i, &adapter1); + var res = factory1->EnumAdapters1(i, &adapter1); - Exception? exception = Marshal.GetExceptionForHR(res); + var exception = Marshal.GetExceptionForHR(res); if (exception != null) break; AdapterDesc1 adapterDesc = default; @@ -95,10 +95,10 @@ public class SystemParametersConstructor //https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiadapter1-getdesc1 SilkMarshal.ThrowHResult(adapter1->GetDesc1(&adapterDesc)); - ulong systemMemory = (ulong)adapterDesc.DedicatedSystemMemory; - ulong videoMemory = (ulong)adapterDesc.DedicatedVideoMemory; + var systemMemory = (ulong)adapterDesc.DedicatedSystemMemory; + var videoMemory = (ulong)adapterDesc.DedicatedVideoMemory; - ulong maxMemory = Math.Max(systemMemory, videoMemory); + var maxMemory = Math.Max(systemMemory, videoMemory); if (maxMemory > dxgiMemory) dxgiMemory = maxMemory; @@ -117,7 +117,7 @@ public class SystemParametersConstructor } } - MEMORYSTATUSEX? memory = GetMemoryStatus(); + var memory = GetMemoryStatus(); var p = new SystemParameters { ScreenWidth = width, diff --git a/Wabbajack.App.Blazor/Wabbajack.App.Blazor.csproj b/Wabbajack.App.Blazor/Wabbajack.App.Blazor.csproj index ee422013..893137c6 100644 --- a/Wabbajack.App.Blazor/Wabbajack.App.Blazor.csproj +++ b/Wabbajack.App.Blazor/Wabbajack.App.Blazor.csproj @@ -22,6 +22,7 @@ + diff --git a/Wabbajack.App.Blazor/wwwroot/index.html b/Wabbajack.App.Blazor/wwwroot/index.html index f586281b..b8b4fed3 100644 --- a/Wabbajack.App.Blazor/wwwroot/index.html +++ b/Wabbajack.App.Blazor/wwwroot/index.html @@ -32,6 +32,5 @@
- - \ No newline at end of file + diff --git a/Wabbajack.Common/CustomObservable.cs b/Wabbajack.Common/CustomObservable.cs new file mode 100644 index 00000000..e076a561 --- /dev/null +++ b/Wabbajack.Common/CustomObservable.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Reactive; + +namespace Wabbajack.Common; + +public class CustomObservable : ObservableBase +{ + private readonly List> _observers = new(); + + private T _value; + public T Value + { + get => _value; + set + { + if (EqualityComparer.Default.Equals(value, _value)) return; + _value = value; + + foreach (var observer in _observers) + { + observer.OnNext(value); + } + } + } + + public CustomObservable(T value) + { + _value = value; + } + + protected override IDisposable SubscribeCore(IObserver observer) + { + if (!_observers.Contains(observer)) + { + _observers.Add(observer); + observer.OnNext(Value); + } + + return new Unsubscriber(_observers, observer); + } +} + +internal sealed class Unsubscriber : IDisposable +{ + private readonly List> _observers; + private readonly IObserver _observer; + + public Unsubscriber(List> observers, IObserver observer) + { + _observers = observers; + _observer = observer; + } + + public void Dispose() + { + if (_observers.Contains(_observer)) _observers.Remove(_observer); + } +} \ No newline at end of file From e7b7350e76e5eac3052a968ef8b32915402e514a Mon Sep 17 00:00:00 2001 From: erri120 Date: Fri, 21 Jan 2022 14:54:22 +0100 Subject: [PATCH 2/5] Update logging --- Wabbajack.App.Blazor/App.xaml.cs | 36 ++++- .../Components/VirtualLogger.razor | 23 ++- Wabbajack.App.Blazor/MainWindow.xaml.cs | 22 +-- Wabbajack.App.Blazor/Models/Install.cs | 3 - Wabbajack.App.Blazor/Models/LoggerProvider.cs | 149 ------------------ Wabbajack.App.Blazor/Pages/Configure.razor | 2 +- Wabbajack.App.Blazor/Pages/Configure.razor.cs | 2 - .../Wabbajack.App.Blazor.csproj | 2 + 8 files changed, 42 insertions(+), 197 deletions(-) delete mode 100644 Wabbajack.App.Blazor/Models/Install.cs delete mode 100644 Wabbajack.App.Blazor/Models/LoggerProvider.cs diff --git a/Wabbajack.App.Blazor/App.xaml.cs b/Wabbajack.App.Blazor/App.xaml.cs index 66bbac5b..bc19d3d7 100644 --- a/Wabbajack.App.Blazor/App.xaml.cs +++ b/Wabbajack.App.Blazor/App.xaml.cs @@ -3,11 +3,13 @@ using System.Windows; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Wabbajack.App.Blazor.Models; +using NLog; +using NLog.Extensions.Logging; +using NLog.Targets; using Wabbajack.App.Blazor.State; using Wabbajack.App.Blazor.Utility; -using Wabbajack.DTOs; using Wabbajack.Services.OSIntegrated; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Wabbajack.App.Blazor; @@ -18,23 +20,41 @@ public partial class App public App() { _serviceProvider = Host.CreateDefaultBuilder(Array.Empty()) - .ConfigureLogging(loggingBuilder => - { - loggingBuilder.ClearProviders(); - }) + .ConfigureLogging(SetupLogging) .ConfigureServices(services => ConfigureServices(services)) .Build() .Services; } + private static void SetupLogging(ILoggingBuilder loggingBuilder) + { + var config = new NLog.Config.LoggingConfiguration(); + + var fileTarget = new FileTarget("file") + { + FileName = "log.log" + }; + + var consoleTarget = new ConsoleTarget("console"); + + var uiTarget = new MemoryTarget("ui"); + + config.AddRuleForAllLevels(fileTarget); + config.AddRuleForAllLevels(consoleTarget); + config.AddRuleForAllLevels(uiTarget); + + loggingBuilder.ClearProviders(); + loggingBuilder.SetMinimumLevel(LogLevel.Trace); + loggingBuilder.AddNLog(config); + } + private static IServiceCollection ConfigureServices(IServiceCollection services) { services.AddOSIntegrated(); services.AddBlazorWebView(); - services.AddAllSingleton(); services.AddTransient(); services.AddSingleton(); - services.AddSingleton(typeof(IStateContainer), typeof(StateContainer)); + services.AddSingleton(); return services; } diff --git a/Wabbajack.App.Blazor/Components/VirtualLogger.razor b/Wabbajack.App.Blazor/Components/VirtualLogger.razor index 2a1832fb..f2d897a2 100644 --- a/Wabbajack.App.Blazor/Components/VirtualLogger.razor +++ b/Wabbajack.App.Blazor/Components/VirtualLogger.razor @@ -1,10 +1,10 @@ -@using Wabbajack.App.Blazor.Models - +@using NLog +@using NLog.Targets @namespace Wabbajack.App.Blazor.Components
- - @logItem.LongMessage + + @logItem
@@ -13,16 +13,11 @@ // TODO: [Low] More parameters to customise the logger. E.g. Reverse order. // TODO: [High] Find a way to auto-scroll. (JS interop?) - [Parameter] - public IObservable Messages { get; set; } + private MemoryTarget? _memoryTarget; + private ICollection Logs => _memoryTarget?.Logs ?? Array.Empty(); - private List _consoleLog = new(); - - protected override async Task OnInitializedAsync() - { - Messages.Subscribe(_consoleLog.Add); - - await base.OnInitializedAsync(); + protected override void OnInitialized() + { + _memoryTarget = LogManager.Configuration.FindTargetByName("ui"); } - } diff --git a/Wabbajack.App.Blazor/MainWindow.xaml.cs b/Wabbajack.App.Blazor/MainWindow.xaml.cs index 782b736a..074c4cfa 100644 --- a/Wabbajack.App.Blazor/MainWindow.xaml.cs +++ b/Wabbajack.App.Blazor/MainWindow.xaml.cs @@ -1,31 +1,13 @@ using System; -using Microsoft.AspNetCore.Components; -using Microsoft.Extensions.Logging; -using Wabbajack.App.Blazor.Models; using Wabbajack.App.Blazor.State; -using Wabbajack.App.Blazor.Utility; -using Wabbajack.Common; -using Wabbajack.Installer; -using Wabbajack.Paths.IO; namespace Wabbajack.App.Blazor; public partial class MainWindow { - private readonly ILogger _logger; - private readonly LoggerProvider _loggerProvider; - private readonly SystemParametersConstructor _systemParams; - private readonly IStateContainer _stateContainer; - - public MainWindow(ILogger logger, IServiceProvider serviceProvider, LoggerProvider loggerProvider, - SystemParametersConstructor systemParams, IStateContainer stateContainer) + public MainWindow(IServiceProvider serviceProvider, IStateContainer stateContainer) { - _logger = logger; - _loggerProvider = loggerProvider; - _systemParams = systemParams; - _stateContainer = stateContainer; - - _stateContainer.TaskBarStateObservable.Subscribe(state => + stateContainer.TaskBarStateObservable.Subscribe(state => { Dispatcher.InvokeAsync(() => { diff --git a/Wabbajack.App.Blazor/Models/Install.cs b/Wabbajack.App.Blazor/Models/Install.cs deleted file mode 100644 index c8adcc28..00000000 --- a/Wabbajack.App.Blazor/Models/Install.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Wabbajack.App.Blazor.Models; - -public class Install { } diff --git a/Wabbajack.App.Blazor/Models/LoggerProvider.cs b/Wabbajack.App.Blazor/Models/LoggerProvider.cs deleted file mode 100644 index af1ddc52..00000000 --- a/Wabbajack.App.Blazor/Models/LoggerProvider.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Collections.Immutable; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.IO; -using System.Reactive.Disposables; -using System.Reactive.Subjects; -using System.Text; -using System.Threading; -using DynamicData; -using Microsoft.Extensions.Logging; -using Wabbajack.Paths; -using Wabbajack.Paths.IO; -using Wabbajack.Services.OSIntegrated; - -namespace Wabbajack.App.Blazor.Models; - -public class LoggerProvider : ILoggerProvider -{ - private readonly RelativePath _appName; - private readonly Configuration _configuration; - private readonly CompositeDisposable _disposables; - private readonly Stream _logFile; - private readonly StreamWriter _logStream; - - public readonly ReadOnlyObservableCollection _messagesFiltered; - private readonly DateTime _startupTime; - - private long _messageId; - private readonly SourceCache _messageLog = new(m => m.MessageId); - private readonly Subject _messages = new(); - - public LoggerProvider(Configuration configuration) - { - _startupTime = DateTime.UtcNow; - _configuration = configuration; - _configuration.LogLocation.CreateDirectory(); - - _disposables = new CompositeDisposable(); - - // Messages - // .ObserveOnGuiThread() - // .Subscribe(m => _messageLog.AddOrUpdate(m)); - - Messages.Subscribe(LogToFile); - - _messageLog.Connect() - .Bind(out _messagesFiltered) - .Subscribe(); - - _appName = typeof(LoggerProvider).Assembly.Location.ToAbsolutePath().FileName; - LogPath = _configuration.LogLocation.Combine($"{_appName}.current.log"); - _logFile = LogPath.Open(FileMode.Append, FileAccess.Write); - - _logStream = new StreamWriter(_logFile, Encoding.UTF8); - } - - public IObservable Messages => _messages; - public AbsolutePath LogPath { get; } - public ReadOnlyObservableCollection MessageLog => _messagesFiltered; - - public void Dispose() - { - _disposables.Dispose(); - } - - public ILogger CreateLogger(string categoryName) - { - return new Logger(this, categoryName); - } - - private void LogToFile(ILogMessage logMessage) - { - var line = $"[{logMessage.TimeStamp - _startupTime}] {logMessage.LongMessage}"; - lock (_logStream) - { - _logStream.Write(line); - _logStream.Flush(); - } - } - - private long NextMessageId() - { - return Interlocked.Increment(ref _messageId); - } - - public class Logger : ILogger - { - private readonly string _categoryName; - private readonly LoggerProvider _provider; - private ImmutableList Scopes = ImmutableList.Empty; - - public Logger(LoggerProvider provider, string categoryName) - { - _categoryName = categoryName; - _provider = provider; - } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, - Func formatter) - { - Debug.WriteLine($"{logLevel} - {formatter(state, exception)}"); - _provider._messages.OnNext(new LogMessage(DateTime.UtcNow, _provider.NextMessageId(), logLevel, - eventId, state, exception, formatter)); - } - - public bool IsEnabled(LogLevel logLevel) - { - return true; - } - - public IDisposable BeginScope(TState state) - { - Scopes = Scopes.Add(state); - return Disposable.Create(() => Scopes = Scopes.Remove(state)); - } - } - - public interface ILogMessage - { - long MessageId { get; } - - string ShortMessage { get; } - DateTime TimeStamp { get; } - string LongMessage { get; } - } - - private record LogMessage(DateTime TimeStamp, long MessageId, LogLevel LogLevel, EventId EventId, - TState State, Exception? Exception, Func Formatter) : ILogMessage - { - public string ShortMessage => Formatter(State, Exception); - - public string LongMessage - { - get - { - var sb = new StringBuilder(); - sb.AppendLine(ShortMessage); - if (Exception != null) - { - sb.Append("Exception: "); - sb.Append(Exception); - } - - return sb.ToString(); - } - } - } -} diff --git a/Wabbajack.App.Blazor/Pages/Configure.razor b/Wabbajack.App.Blazor/Pages/Configure.razor index fc114df9..2f032d82 100644 --- a/Wabbajack.App.Blazor/Pages/Configure.razor +++ b/Wabbajack.App.Blazor/Pages/Configure.razor @@ -41,7 +41,7 @@ @if (InstallState == InstallState.Installing) {
- +
} else diff --git a/Wabbajack.App.Blazor/Pages/Configure.razor.cs b/Wabbajack.App.Blazor/Pages/Configure.razor.cs index ac649895..839e693d 100644 --- a/Wabbajack.App.Blazor/Pages/Configure.razor.cs +++ b/Wabbajack.App.Blazor/Pages/Configure.razor.cs @@ -12,7 +12,6 @@ using Wabbajack.Services.OSIntegrated; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.JSInterop; -using Wabbajack.App.Blazor.Models; using Wabbajack.App.Blazor.State; namespace Wabbajack.App.Blazor.Pages; @@ -26,7 +25,6 @@ public partial class Configure [Inject] private SystemParametersConstructor ParametersConstructor { get; set; } = default!; [Inject] private IGameLocator GameLocator { get; set; } = default!; [Inject] private SettingsManager SettingsManager { get; set; } = default!; - [Inject] private LoggerProvider LoggerProvider { get; set; } = default!; [Inject] private JSRuntime JSRuntime { get; set; } = default!; private ModList? Modlist => StateContainer.Modlist; diff --git a/Wabbajack.App.Blazor/Wabbajack.App.Blazor.csproj b/Wabbajack.App.Blazor/Wabbajack.App.Blazor.csproj index 893137c6..94ebf08a 100644 --- a/Wabbajack.App.Blazor/Wabbajack.App.Blazor.csproj +++ b/Wabbajack.App.Blazor/Wabbajack.App.Blazor.csproj @@ -20,6 +20,8 @@ + + From 3a137db1d825665d348a512cd14e92a2ccd16e61 Mon Sep 17 00:00:00 2001 From: erri120 Date: Fri, 21 Jan 2022 15:13:45 +0100 Subject: [PATCH 3/5] Discard Blazor internal logs --- Wabbajack.App.Blazor/App.xaml.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Wabbajack.App.Blazor/App.xaml.cs b/Wabbajack.App.Blazor/App.xaml.cs index bc19d3d7..1f4f00fc 100644 --- a/Wabbajack.App.Blazor/App.xaml.cs +++ b/Wabbajack.App.Blazor/App.xaml.cs @@ -39,6 +39,7 @@ public partial class App var uiTarget = new MemoryTarget("ui"); + config.AddRuleForAllLevels(new NullTarget("blackhole"), "Microsoft.AspNetCore.Components.*", true); config.AddRuleForAllLevels(fileTarget); config.AddRuleForAllLevels(consoleTarget); config.AddRuleForAllLevels(uiTarget); From 9187fa23072fc2f7fe5fcfb238f65e800683b360 Mon Sep 17 00:00:00 2001 From: erri120 Date: Fri, 21 Jan 2022 15:44:05 +0100 Subject: [PATCH 4/5] Fixes --- Wabbajack.App.Blazor/App.xaml.cs | 9 +++------ Wabbajack.App.Blazor/Pages/Configure.razor.cs | 3 +-- Wabbajack.App.Blazor/wwwroot/index.html | 7 +++++++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Wabbajack.App.Blazor/App.xaml.cs b/Wabbajack.App.Blazor/App.xaml.cs index 1f4f00fc..2c5ad75e 100644 --- a/Wabbajack.App.Blazor/App.xaml.cs +++ b/Wabbajack.App.Blazor/App.xaml.cs @@ -3,13 +3,11 @@ using System.Windows; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using NLog; using NLog.Extensions.Logging; using NLog.Targets; using Wabbajack.App.Blazor.State; using Wabbajack.App.Blazor.Utility; using Wabbajack.Services.OSIntegrated; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Wabbajack.App.Blazor; @@ -34,12 +32,11 @@ public partial class App { FileName = "log.log" }; - var consoleTarget = new ConsoleTarget("console"); - var uiTarget = new MemoryTarget("ui"); - - config.AddRuleForAllLevels(new NullTarget("blackhole"), "Microsoft.AspNetCore.Components.*", true); + var blackholeTarget = new NullTarget("blackhole"); + + config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Debug, blackholeTarget, "Microsoft.AspNetCore.Components.*", true); config.AddRuleForAllLevels(fileTarget); config.AddRuleForAllLevels(consoleTarget); config.AddRuleForAllLevels(uiTarget); diff --git a/Wabbajack.App.Blazor/Pages/Configure.razor.cs b/Wabbajack.App.Blazor/Pages/Configure.razor.cs index 839e693d..a32f5a14 100644 --- a/Wabbajack.App.Blazor/Pages/Configure.razor.cs +++ b/Wabbajack.App.Blazor/Pages/Configure.razor.cs @@ -25,7 +25,7 @@ public partial class Configure [Inject] private SystemParametersConstructor ParametersConstructor { get; set; } = default!; [Inject] private IGameLocator GameLocator { get; set; } = default!; [Inject] private SettingsManager SettingsManager { get; set; } = default!; - [Inject] private JSRuntime JSRuntime { get; set; } = default!; + [Inject] private IJSRuntime JSRuntime { get; set; } = default!; private ModList? Modlist => StateContainer.Modlist; @@ -35,7 +35,6 @@ public partial class Configure private string StatusText { get; set; } = string.Empty; private InstallState InstallState => StateContainer.InstallState; - // private LoggerProvider.ILogMessage CurrentLog { get; set; } private const string InstallSettingsPrefix = "install-settings-"; diff --git a/Wabbajack.App.Blazor/wwwroot/index.html b/Wabbajack.App.Blazor/wwwroot/index.html index b8b4fed3..f35708e4 100644 --- a/Wabbajack.App.Blazor/wwwroot/index.html +++ b/Wabbajack.App.Blazor/wwwroot/index.html @@ -32,5 +32,12 @@
+ From f1b17df03ae5ca10841a1aec5c03e7d4a8897b17 Mon Sep 17 00:00:00 2001 From: erri120 Date: Fri, 21 Jan 2022 16:11:44 +0100 Subject: [PATCH 5/5] Fixes --- Wabbajack.App.Blazor/App.xaml.cs | 6 +++++- Wabbajack.App.Blazor/Pages/Gallery.razor | 7 +++---- Wabbajack.App.Blazor/Pages/Gallery.razor.cs | 21 ++++++++++--------- .../Utility/SystemParametersConstructor.cs | 3 --- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Wabbajack.App.Blazor/App.xaml.cs b/Wabbajack.App.Blazor/App.xaml.cs index 2c5ad75e..77cba228 100644 --- a/Wabbajack.App.Blazor/App.xaml.cs +++ b/Wabbajack.App.Blazor/App.xaml.cs @@ -35,8 +35,12 @@ public partial class App var consoleTarget = new ConsoleTarget("console"); var uiTarget = new MemoryTarget("ui"); var blackholeTarget = new NullTarget("blackhole"); + + if (!string.Equals("TRUE", Environment.GetEnvironmentVariable("DEBUG_BLAZOR", EnvironmentVariableTarget.Process), StringComparison.OrdinalIgnoreCase)) + { + config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Debug, blackholeTarget, "Microsoft.AspNetCore.Components.*", true); + } - config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Debug, blackholeTarget, "Microsoft.AspNetCore.Components.*", true); config.AddRuleForAllLevels(fileTarget); config.AddRuleForAllLevels(consoleTarget); config.AddRuleForAllLevels(uiTarget); diff --git a/Wabbajack.App.Blazor/Pages/Gallery.razor b/Wabbajack.App.Blazor/Pages/Gallery.razor index 6c8c42eb..88c96f22 100644 --- a/Wabbajack.App.Blazor/Pages/Gallery.razor +++ b/Wabbajack.App.Blazor/Pages/Gallery.razor @@ -1,6 +1,5 @@ @page "/gallery" -@using Wabbajack.RateLimiter @using System.Globalization @namespace Wabbajack.App.Blazor.Pages @@ -36,11 +35,11 @@ } } - @if (_downloadProgress != Percent.Zero && _downloadingMetaData is not null) + @if (DownloadingMetaData is not null) { - +
- +
} diff --git a/Wabbajack.App.Blazor/Pages/Gallery.razor.cs b/Wabbajack.App.Blazor/Pages/Gallery.razor.cs index b1d911b0..27150139 100644 --- a/Wabbajack.App.Blazor/Pages/Gallery.razor.cs +++ b/Wabbajack.App.Blazor/Pages/Gallery.razor.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Reactive.Linq; -using System.Threading; using System.Threading.Tasks; using System.Windows.Shell; using Microsoft.AspNetCore.Components; @@ -11,8 +9,6 @@ using Microsoft.Extensions.Logging; using Wabbajack.App.Blazor.State; using Wabbajack.Common; using Wabbajack.DTOs; -using Wabbajack.Networking.WabbajackClientApi; -using Wabbajack.Paths; using Wabbajack.Paths.IO; using Wabbajack.RateLimiter; using Wabbajack.Services.OSIntegrated.Services; @@ -26,8 +22,8 @@ public partial class Gallery [Inject] private NavigationManager NavigationManager { get; set; } = default!; [Inject] private ModListDownloadMaintainer Maintainer { get; set; } = default!; - private Percent _downloadProgress = Percent.Zero; - private ModlistMetadata? _downloadingMetaData; + private Percent DownloadProgress { get; set; } = Percent.Zero; + private ModlistMetadata? DownloadingMetaData { get; set; } private IEnumerable Modlists => StateContainer.Modlists; @@ -52,13 +48,13 @@ public partial class Gallery _shouldRender = true; } - private async void OnClickDownload(ModlistMetadata metadata) + private async Task OnClickDownload(ModlistMetadata metadata) { // GlobalState.NavigationAllowed = !GlobalState.NavigationAllowed; await Download(metadata); } - private async void OnClickInformation(ModlistMetadata metadata) + private async Task OnClickInformation(ModlistMetadata metadata) { // TODO: [High] Implement information modal. } @@ -66,8 +62,10 @@ public partial class Gallery private async Task Download(ModlistMetadata metadata) { StateContainer.NavigationAllowed = false; - _downloadingMetaData = metadata; + DownloadingMetaData = metadata; + // TODO: download progress should be in ProgressBar component so it can refresh independently + try { var (progress, task) = Maintainer.DownloadModlist(metadata); @@ -77,13 +75,16 @@ public partial class Gallery .Throttle(TimeSpan.FromMilliseconds(100)) .Subscribe(p => { - _downloadProgress = p; + DownloadProgress = p; StateContainer.TaskBarState = new TaskBarState { Description = $"Downloading {metadata.Title}", State = TaskbarItemProgressState.Indeterminate, ProgressValue = p.Value }; + + InvokeAsync(StateHasChanged); + // Dispatcher.CreateDefault().InvokeAsync(StateHasChanged); }, () => { StateContainer.TaskBarState = new TaskBarState(); }); await task; diff --git a/Wabbajack.App.Blazor/Utility/SystemParametersConstructor.cs b/Wabbajack.App.Blazor/Utility/SystemParametersConstructor.cs index ca33aedb..8c032834 100644 --- a/Wabbajack.App.Blazor/Utility/SystemParametersConstructor.cs +++ b/Wabbajack.App.Blazor/Utility/SystemParametersConstructor.cs @@ -1,16 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Text; using Microsoft.Extensions.Logging; using PInvoke; using Silk.NET.Core.Native; using Silk.NET.DXGI; using Wabbajack.Common; using Wabbajack.Installer; -using Wabbajack; using Wabbajack.Paths.IO; using static PInvoke.User32; using UnmanagedType = System.Runtime.InteropServices.UnmanagedType;