Checkbox and Logger components. Layout changes to Configure page.

Also added TODOs and a basic .editorconfig to suppress annoying warnings.
This commit is contained in:
Unnoen 2022-01-18 03:45:52 +11:00
parent f2e29a9ab1
commit dc9c327652
No known key found for this signature in database
GPG Key ID: 8F8E42252BA20553
19 changed files with 241 additions and 75 deletions

22
.editorconfig Normal file
View File

@ -0,0 +1,22 @@
root = true
# All files
[*]
indent_style = space
indent_size = 4
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

View File

@ -1 +1 @@
 .sonarqube

View File

@ -5,7 +5,6 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-content: center; align-content: center;
margin: 1rem;
.supertitle { .supertitle {
margin-left: 0.5rem; margin-left: 0.5rem;

View File

@ -4,7 +4,6 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-content: center; align-content: center;
margin: 1rem;
.mod-feature { .mod-feature {
margin-left: -10px; margin-left: -10px;

View File

@ -1,5 +0,0 @@
<h3>Logger</h3>
@code {
}

View File

@ -14,7 +14,7 @@ using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter; using Wabbajack.RateLimiter;
using Wabbajack.Services.OSIntegrated.Services; using Wabbajack.Services.OSIntegrated.Services;
// TODO: [High] Move logic to Gallery page.
namespace Wabbajack.App.Blazor.Components namespace Wabbajack.App.Blazor.Components
{ {
public partial class ModlistItem public partial class ModlistItem

View File

@ -0,0 +1,25 @@
<label class="option">
@Label
<input type="checkbox" checked="@IsChecked" @onchange="CheckBoxChanged">
<span class="checkmark"></span>
</label>
@code {
// TODO: [Low] Implement parameters to customize style.
// TODO: [High] Implement a way to set a passed bool without using callback function.
[Parameter]
public string Label { get; set; }
[Parameter]
public EventCallback<bool> OnChecked { get; set; }
private bool IsChecked { get; set; }
private async Task CheckBoxChanged(ChangeEventArgs e)
{
IsChecked = (bool)(e.Value ?? false);
await OnChecked.InvokeAsync(IsChecked);
}
}

View File

@ -0,0 +1,56 @@
@import "../Shared/Globals.scss";
$checkbox-background: rgba(255, 255, 255, 0.2);
$checkbox-background-hover: darkgrey;
$checkbox-background-checked: $accent-color;
$checkbox-size: 0.75rem;
.option {
position: relative;
display: block;
margin: 0.25rem;
padding-left: 2rem;
cursor: pointer;
user-select: none;
&:hover input ~ .checkmark {
background-color: $checkbox-background-hover;
}
input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
&:checked ~ .checkmark {
background-color: $checkbox-background-checked;
&:after {
display: block;
left: calc(0.5 * #{$checkbox-size});
top: calc(0.25 * #{$checkbox-size});
width: calc(0.25 * #{$checkbox-size});
height: calc(0.65 * #{$checkbox-size});
border: solid white;
border-width: 0 3px 3px 0;
transform: rotate(45deg);
}
}
}
.checkmark {
position: absolute;
top: 0;
left: 0;
height: calc(1.5 * #{$checkbox-size});
width: calc(1.5 * #{$checkbox-size});
background-color: $checkbox-background;
&:after {
content: "";
position: absolute;
display: none;
}
}
}

View File

@ -1,4 +1,5 @@
<div id="side-bar"> <div id="side-bar">
@* TODO: [Low] Replace logo with SVG? *@
<img class="logo" src="images/Logo_Dark_Transparent.png" alt="Wabbajack Logo"> <img class="logo" src="images/Logo_Dark_Transparent.png" alt="Wabbajack Logo">
<div class="socials"> <div class="socials">
<img src="images/icons/patreon.svg" alt=""> <img src="images/icons/patreon.svg" alt="">
@ -6,6 +7,3 @@
<img src="images/icons/discord.svg" alt=""> <img src="images/icons/discord.svg" alt="">
</div> </div>
</div> </div>
@code {
}

View File

@ -1,4 +1,5 @@
<header id="top-bar"> @* TODO: [Low] Indicate current page. *@
<header id="top-bar">
<nav> <nav>
<ul> <ul>
<li> <li>

View File

@ -0,0 +1,26 @@
@using Wabbajack.App.Blazor.Models
<div id="virtual-logger">
<Virtualize Items="@_consoleLog" Context="logItem" OverscanCount="3">
<span @key="logItem.MessageId">@logItem.LongMessage</span>
</Virtualize>
</div>
@code {
// 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<LoggerProvider.ILogMessage> Messages { get; set; }
private List<LoggerProvider.ILogMessage> _consoleLog = new();
protected override async Task OnInitializedAsync()
{
Messages.Subscribe(_consoleLog.Add);
await base.OnInitializedAsync();
}
}

View File

@ -0,0 +1,23 @@
// TODO: [Low] Logging levels?
#virtual-logger {
height: 100%;
overflow-y: scroll;
width: 100%;
.info {
}
.warn {
}
.error {
}
span {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.85rem;
}
}

View File

@ -29,7 +29,7 @@ namespace Wabbajack.App.Blazor
try try
{ {
// TODO: Not sure how to set this up. // TODO: [Low] Not sure how to set this up.
//_logger.LogInformation("Wabbajack Build - {Sha}", ThisAssembly.Git.Sha); //_logger.LogInformation("Wabbajack Build - {Sha}", ThisAssembly.Git.Sha);
_logger.LogInformation("Running in {EntryPoint}", KnownFolders.EntryPoint); _logger.LogInformation("Running in {EntryPoint}", KnownFolders.EntryPoint);

View File

@ -3,37 +3,52 @@
@namespace Wabbajack.App.Blazor.Pages @namespace Wabbajack.App.Blazor.Pages
<div id="content"> <div id="content">
<div class="image"> <div class="install-background">
<img src="@Image" alt=""> <img src="@Image" alt="">
</div> </div>
<div class="list"> <div class="list">
@if (!string.IsNullOrEmpty(ModList.Name)) @* TODO: [High] Find a cleaner way to show/hide components based on state. *@
{ @* TODO: [Low] Split each "side" into their own components? *@
if (_installState.Value.CurrentInstallState != InstallState.InstallStateEnum.Installing) <div class="left-side">
@if (!string.IsNullOrEmpty(ModList.Name))
{ {
<InfoBlock Title="@ModList.Name" Subtitle="@ModList.Author" Comment="@ModList.Version.ToString()" Description="@ModList.Description"/> if (_installState.Value.CurrentInstallState != InstallState.InstallStateEnum.Installing)
{
<InfoBlock Title="@ModList.Name" Subtitle="@ModList.Author" Comment="@ModList.Version.ToString()" Description="@ModList.Description"/>
}
else if (_installState.Value.CurrentInstallState == InstallState.InstallStateEnum.Installing)
{
<InfoBlock Supertitle="Installing..." Title="@ModList.Name" Subtitle="@StatusText"/>
// TODO: [Low] Step logging.
}
} }
else if (_installState.Value.CurrentInstallState == InstallState.InstallStateEnum.Installing) </div>
<div class="right-side">
@if (!string.IsNullOrEmpty(Image))
{ {
<InfoBlock Supertitle="Installing..." Title="@ModList.Name" Subtitle="@StatusText" Description="There will be a log here but I need to figure out logging and non-blocking UI stuff first."/> if (_installState.Value.CurrentInstallState == InstallState.InstallStateEnum.Configuration)
{
<InfoImage Image="@Image"/>
}
else if (_installState.Value.CurrentInstallState == InstallState.InstallStateEnum.Installing)
{
// TODO: [Low] Implement featured mod slideshow.
<InfoImage Image="@Image" Title="Some Mod Title" Subtitle="Author and others" Description="This mod adds something cool but I'm not going to tell you anything."/>
}
} }
} </div>
@if (!string.IsNullOrEmpty(Image))
{
if (_installState.Value.CurrentInstallState == InstallState.InstallStateEnum.Configuration)
{
<InfoImage Image="@Image"/>
}
else if (_installState.Value.CurrentInstallState == InstallState.InstallStateEnum.Installing)
{
<InfoImage Image="@Image" Title="Some Mod Title" Subtitle="Author and others" Description="This mod adds something cool but I'm not going to tell you anything."/>
}
}
</div> </div>
@if (_installState.Value.CurrentInstallState == InstallState.InstallStateEnum.Installing)
{
<div class="logger-container">
<VirtualLogger Messages="_loggerProvider.Messages"/>
</div>
}
@if (_installState.Value.CurrentInstallState != InstallState.InstallStateEnum.Installing) @if (_installState.Value.CurrentInstallState != InstallState.InstallStateEnum.Installing)
{ {
<div class="settings"> <div class="settings">
<div class="locations"> <div class="locations">
@* TODO: [High] Turn path selectors into components. *@
<div class="labels"> <div class="labels">
<span>Target Modlist</span> <span>Target Modlist</span>
<span>Install Location</span> <span>Install Location</span>
@ -47,29 +62,10 @@
</div> </div>
</div> </div>
<div class="options"> <div class="options">
<label class="option"> <OptionCheckbox Label="Overwrite Installation"/>
Overwrite Installation <OptionCheckbox Label="NTFS Compression"/>
<input type="checkbox" checked="checked"> <OptionCheckbox Label="Do a sweet trick"/>
<span class="checkmark"></span> <OptionCheckbox Label="Something else"/>
</label>
<label class="option">
<input type="checkbox">
<span class="checkmark"></span>
NTFS Compression
</label>
<label class="option">
<input type="checkbox">
<span class="checkmark"></span>
Do a sweet trick
</label>
<label class="option">
<input type="checkbox">
<span class="checkmark"></span>
Something else
</label>
</div> </div>
<div class="install"> <div class="install">
<img src="images/icons/play.svg" @onclick="Install" alt="Browse Gallery"> <img src="images/icons/play.svg" @onclick="Install" alt="Browse Gallery">

View File

@ -2,7 +2,6 @@
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Fluxor; using Fluxor;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Wabbajack.App.Blazor.Store; using Wabbajack.App.Blazor.Store;
@ -14,6 +13,9 @@ using Wabbajack.App.Blazor.Utility;
using Wabbajack.Downloaders.GameFile; using Wabbajack.Downloaders.GameFile;
using Wabbajack.Hashing.xxHash64; using Wabbajack.Hashing.xxHash64;
using Wabbajack.Services.OSIntegrated; using Wabbajack.Services.OSIntegrated;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Wabbajack.App.Blazor.Models;
namespace Wabbajack.App.Blazor.Pages namespace Wabbajack.App.Blazor.Pages
{ {
@ -27,6 +29,7 @@ namespace Wabbajack.App.Blazor.Pages
[Inject] private SystemParametersConstructor _parametersConstructor { get; set; } [Inject] private SystemParametersConstructor _parametersConstructor { get; set; }
[Inject] private IGameLocator _gameLocator { get; set; } [Inject] private IGameLocator _gameLocator { get; set; }
[Inject] private SettingsManager _settingsManager { get; set; } [Inject] private SettingsManager _settingsManager { get; set; }
[Inject] private LoggerProvider _loggerProvider { get; set; }
private string Image { get; set; } 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 ModList ModList { get; set; } = new(); // Init a new modlist so we can listen for changes in Blazor components.
@ -34,7 +37,8 @@ namespace Wabbajack.App.Blazor.Pages
private AbsolutePath InstallPath { get; set; } private AbsolutePath InstallPath { get; set; }
private AbsolutePath DownloadPath { get; set; } private AbsolutePath DownloadPath { get; set; }
private string StatusText { get; set; } private string StatusText { get; set; }
private LoggerProvider.ILogMessage CurrentLog { get; set; }
private const string InstallSettingsPrefix = "install-settings-"; private const string InstallSettingsPrefix = "install-settings-";
@ -101,7 +105,7 @@ namespace Wabbajack.App.Blazor.Pages
private async Task Install() private async Task Install()
{ {
_dispatcher.Dispatch(new UpdateInstallState(InstallState.InstallStateEnum.Installing, ModList, ModListPath, InstallPath, DownloadPath)); _dispatcher.Dispatch(new UpdateInstallState(InstallState.InstallStateEnum.Installing, ModList, ModListPath, InstallPath, DownloadPath));
Task.Run(BeginInstall); await Task.Run(BeginInstall);
} }
private async Task BeginInstall() private async Task BeginInstall()
@ -136,6 +140,7 @@ namespace Wabbajack.App.Blazor.Pages
InvokeAsync(StateHasChanged); InvokeAsync(StateHasChanged);
} }
}; };
await installer.Begin(CancellationToken.None); await installer.Begin(CancellationToken.None);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -25,7 +25,7 @@ $checkbox-size: 0.75rem;
color: white; color: white;
flex-direction: column; flex-direction: column;
.image { .install-background {
position: absolute; position: absolute;
width: calc(100% - #{$sidebar-width}); width: calc(100% - #{$sidebar-width});
height: calc(100% - #{$header-height}); height: calc(100% - #{$header-height});
@ -43,7 +43,19 @@ $checkbox-size: 0.75rem;
display: flex; display: flex;
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
align-items: center;
.left-side, .right-side {
flex: 1;
}
} }
.logger-container {
height: 200px;
width: 100%;
padding: 0.5rem;
background: rgb(0 0 0 / 20%);
color: lightgrey;
border: solid 1px black;
}
.settings { .settings {
font-size: 0.85rem; font-size: 0.85rem;

View File

@ -37,7 +37,7 @@
} }
catch (Exception ex) catch (Exception ex)
{ {
//TODO: Figure out why an exception is thrown on first navigation. //TODO: [Critical] Figure out why an exception is thrown on first navigation.
Debug.Print(ex.Message); Debug.Print(ex.Message);
_logger.LogError(ex, "Error while loading lists"); _logger.LogError(ex, "Error while loading lists");
} }

View File

@ -8,6 +8,10 @@ namespace Wabbajack.App.Blazor.Utility;
public static class Dialog public static class Dialog
{ {
/*
* TODO: [Critical] CommonOpenFileDialog.ShowDialog() causes UI freeze and crash.
* This method seems to alleviate it, but it still occasionally happens.
*/
public static async Task<AbsolutePath?> ShowDialogNonBlocking(bool isFolderPicker = false) public static async Task<AbsolutePath?> ShowDialogNonBlocking(bool isFolderPicker = false)
{ {
return await Task.Factory.StartNew(() => return await Task.Factory.StartNew(() =>

View File

@ -119,11 +119,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wabbajack.Launcher", "Wabba
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wabbajack.App.Wpf", "Wabbajack.App.Wpf\Wabbajack.App.Wpf.csproj", "{372B2DD2-EAA3-4E18-98A7-B9838C7B41F4}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wabbajack.App.Wpf", "Wabbajack.App.Wpf\Wabbajack.App.Wpf.csproj", "{372B2DD2-EAA3-4E18-98A7-B9838C7B41F4}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wabbajack.Networking.Steam", "Wabbajack.Networking.Steam\Wabbajack.Networking.Steam.csproj", "{AB9A5C22-10CC-4EE0-A808-FB1DC9E24247}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wabbajack.Networking.Steam", "Wabbajack.Networking.Steam\Wabbajack.Networking.Steam.csproj", "{AB9A5C22-10CC-4EE0-A808-FB1DC9E24247}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wabbajack.Networking.Steam.Test", "Wabbajack.Networking.Steam.Test\Wabbajack.Networking.Steam.Test.csproj", "{D6351587-CAF6-4CB6-A2BD-5368E69F297C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wabbajack.Networking.Steam.Test", "Wabbajack.Networking.Steam.Test\Wabbajack.Networking.Steam.Test.csproj", "{D6351587-CAF6-4CB6-A2BD-5368E69F297C}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wabbajack.App.Blazor", "Wabbajack.App.Blazor\Wabbajack.App.Blazor.csproj", "{C6E9B15D-510F-4074-AB1C-069F36BA4622}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wabbajack.App.Blazor", "Wabbajack.App.Blazor\Wabbajack.App.Blazor.csproj", "{C6E9B15D-510F-4074-AB1C-069F36BA4622}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{18E36813-CB53-4172-8FF3-EFE3B9B30A5F}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution