Restructure installing stage into three pages. Changed some design.

Added getBlobUrlFromStream JavaScript function so we can reuse the URL.
This commit is contained in:
Unnoen 2022-01-27 18:55:07 +11:00
parent d1e64e910c
commit 47ffa30222
No known key found for this signature in database
GPG Key ID: 8F8E42252BA20553
12 changed files with 362 additions and 162 deletions

View File

@ -28,7 +28,7 @@
{ {
{"Play", Play.Route}, {"Play", Play.Route},
{"Gallery", Gallery.Route}, {"Gallery", Gallery.Route},
{"Install", Install.Route}, {"Install", Select.Route},
{"Create", Create.Route} {"Create", Create.Route}
}; };

View File

@ -1,79 +0,0 @@
@page "/configure"
@using Wabbajack.App.Blazor.State
@namespace Wabbajack.App.Blazor.Pages
<div id="content">
<div class="install-background">
<img id="background-image" src="" alt=""/>
</div>
<div class="list">
@if (Modlist is not null)
{
<div class="left-side">
@if (InstallState != InstallState.Installing)
{
<InfoBlock Title="@Modlist.Name" Subtitle="@Modlist.Author" Comment="@Modlist.Version.ToString()" Description="@Modlist.Description"/>
}
else
{
<InfoBlock Supertitle="Installing..." Title="@Modlist.Name" Subtitle="@StatusText"/>
// TODO: [Low] Step logging
}
</div>
<div class="right-side">
@* TODO: whatever this is *@
@* @if (!string.IsNullOrEmpty(Image)) *@
@* { *@
@* if (InstallState != GlobalState.InstallStateEnum.Installing) *@
@* { *@
@* <InfoImage Image="@Image"/> *@
@* } *@
@* else if (InstallState == GlobalState.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>
}
</div>
@if (InstallState == InstallState.Installing)
{
<div class="logger-container">
<VirtualLogger/>
</div>
}
else
{
<div class="settings">
<div class="locations">
@* TODO: [High] Turn path selectors into components. *@
<div class="labels">
<span>Target Modlist</span>
<span>Install Location</span>
<span>Download Location</span>
</div>
<div class="paths">
<span class="modlist-file">@ModlistPath.ToString()</span>
<span class="install-location" @onclick="SelectInstallFolder">@InstallPath.ToString()</span>
<span class="download-location" @onclick="SelectDownloadFolder">@DownloadPath.ToString()</span>
</div>
</div>
<div class="options">
<OptionCheckbox Label="Overwrite Installation"/>
<OptionCheckbox Label="NTFS Compression"/>
<OptionCheckbox Label="Do a sweet trick"/>
<OptionCheckbox Label="Something else"/>
</div>
<div class="install">
<img src="images/icons/play.svg" @onclick="Install" alt="Browse Gallery">
</div>
</div>
}
</div>
@code {
public const string Route = "/configure";
}

View File

@ -0,0 +1,48 @@
@page "/install/configure"
@namespace Wabbajack.App.Blazor.Pages
<div id="content">
<div class="install-background">
<img id="background-image" src="@ModlistImage" alt=""/>
</div>
<div class="list">
@if (Modlist is not null)
{
<div class="left-side">
<InfoBlock Title="@Modlist.Name" Subtitle="@Modlist.Author" Comment="@Modlist.Version.ToString()" Description="@Modlist.Description"/>
</div>
<div class="right-side">
<InfoImage Image="@ModlistImage"/>
</div>
}
</div>
<div class="settings">
<div class="locations">
<div class="labels">
<span>Target Modlist</span>
<span>Install Location</span>
<span>Download Location</span>
</div>
<div class="paths">
<span class="modlist-file">@ModlistPath.ToString()</span>
<span class="install-location" @onclick="SelectInstallFolder">@InstallPath.ToString()</span>
<span class="download-location" @onclick="SelectDownloadFolder">@DownloadPath.ToString()</span>
</div>
</div>
<div class="options">
<OptionCheckbox Label="Overwrite Installation"/>
<OptionCheckbox Label="NTFS Compression"/>
<OptionCheckbox Label="Do a sweet trick"/>
<OptionCheckbox Label="Something else"/>
</div>
<div class="install">
<img src="images/icons/play.svg" @onclick="Install" alt="Install">
</div>
</div>
</div>
@code {
public const string Route = "/install/configure";
}

View File

@ -0,0 +1,131 @@
using System;
using System.IO;
using Microsoft.AspNetCore.Components;
using Wabbajack.DTOs;
using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Installer;
using Wabbajack.Paths;
using Wabbajack.App.Blazor.Utility;
using Wabbajack.Hashing.xxHash64;
using Wabbajack.Services.OSIntegrated;
using System.Threading.Tasks;
using Blazored.Toast.Services;
using Microsoft.Extensions.Logging;
using Microsoft.JSInterop;
using Wabbajack.App.Blazor.State;
namespace Wabbajack.App.Blazor.Pages;
public partial class Configure
{
[Inject] private ILogger<Configure> Logger { get; set; } = default!;
[Inject] private IStateContainer StateContainer { get; set; } = default!;
[Inject] private DTOSerializer DTOs { get; set; } = default!;
[Inject] private SettingsManager SettingsManager { get; set; } = default!;
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
[Inject] private IJSRuntime JSRuntime { get; set; } = default!;
[Inject] private IToastService toastService { get; set; }
private ModList? Modlist => StateContainer.Modlist;
private string ModlistImage => StateContainer.ModlistImage;
private AbsolutePath ModlistPath => StateContainer.ModlistPath;
private AbsolutePath InstallPath => StateContainer.InstallPath;
private AbsolutePath DownloadPath => StateContainer.DownloadPath;
private InstallState InstallState => StateContainer.InstallState;
private const string InstallSettingsPrefix = "install-settings-";
private bool _shouldRender;
protected override bool ShouldRender() => _shouldRender;
protected override async Task OnInitializedAsync()
{
await LoadModlist();
_shouldRender = true;
}
private async Task LoadModlist()
{
try
{
if (ModlistPath == AbsolutePath.Empty) throw new FileNotFoundException("Modlist path was empty.");
var modlist = await StandardInstaller.LoadFromFile(DTOs, ModlistPath);
StateContainer.Modlist = modlist;
}
catch (Exception e)
{
toastService.ShowError("Could not load modlist!");
Logger.LogError(e, "Exception loading Modlist file {Name}", ModlistPath);
NavigationManager.NavigateTo(Select.Route);
return;
}
try
{
var hex = (await ModlistPath.ToString().Hash()).ToHex();
var prevSettings = await SettingsManager.Load<SavedInstallSettings>(InstallSettingsPrefix + hex);
if (prevSettings.ModlistLocation == ModlistPath)
{
StateContainer.ModlistPath = prevSettings.ModlistLocation;
StateContainer.InstallPath = prevSettings.InstallLocation;
StateContainer.DownloadPath = prevSettings.DownloadLocation;
}
}
catch (Exception e)
{
Logger.LogWarning(e, "Exception loading previous settings for {Name}", ModlistPath);
}
try
{
var imageStream = await StandardInstaller.ModListImageStream(ModlistPath);
var dotnetImageStream = new DotNetStreamReference(imageStream);
StateContainer.ModlistImage = new string(await JSRuntime.InvokeAsync<string>("getBlobUrlFromStream", dotnetImageStream));
}
catch (Exception e)
{
toastService.ShowWarning("Could not load modlist image.");
Logger.LogWarning(e, "Exception loading modlist image for {Name}", ModlistPath);
}
}
private async void SelectInstallFolder()
{
try
{
var installPath = await Dialog.ShowDialogNonBlocking(true);
if (installPath is not null) StateContainer.InstallPath = (AbsolutePath) installPath;
}
catch (Exception e)
{
Logger.LogError(e, "Exception selecting install folder");
}
}
private async void SelectDownloadFolder()
{
try
{
var downloadPath = await Dialog.ShowDialogNonBlocking(true);
if (downloadPath is not null) StateContainer.DownloadPath = (AbsolutePath) downloadPath;
}
catch (Exception e)
{
Logger.LogError(e, "Exception selecting download folder");
}
}
private void Install()
{
NavigationManager.NavigateTo(Installing.Route);
}
}
internal class SavedInstallSettings
{
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; }
}

View File

@ -1,4 +1,4 @@
@import "../Shared/Globals.scss"; @import "../../Shared/Globals.scss";
$checkbox-background: rgba(255, 255, 255, 0.2); $checkbox-background: rgba(255, 255, 255, 0.2);
$checkbox-background-hover: darkgrey; $checkbox-background-hover: darkgrey;

View File

@ -0,0 +1,34 @@
@page "/install/installing"
@namespace Wabbajack.App.Blazor.Pages
<div id="content">
<div class="install-background">
<img id="background-image" src="@ModlistImage" alt=""/>
</div>
<div class="list">
@if (Modlist is not null)
{
<div class="left-side">
<CascadingValue Value=this>
<InfoBlock Supertitle="@StatusCategory" Title="@Modlist.Name"/>
<StepLogger ShownSteps="3" Reverse="false" Steps="@StatusStep"/>
</CascadingValue>
</div>
<div class="right-side">
<InfoImage Image="@ModlistImage" Title="Some Mod Title" Subtitle="Author and others" Description="This mod adds something cool but I'm not going to tell you anything."/>
<div id="progressthing1"></div>
<div id="progressthing2"></div>
<div id="progressthing3"></div>
<div id="progressthing4"></div>
</div>
}
</div>
<div class="logger-container">
<VirtualLogger/>
</div>
</div>
@code {
public const string Route = "/install/installing";
}

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Wabbajack.DTOs; using Wabbajack.DTOs;
@ -16,7 +17,7 @@ using Wabbajack.App.Blazor.State;
namespace Wabbajack.App.Blazor.Pages; namespace Wabbajack.App.Blazor.Pages;
public partial class Configure public partial class Installing
{ {
[Inject] private ILogger<Configure> Logger { get; set; } = default!; [Inject] private ILogger<Configure> Logger { get; set; } = default!;
[Inject] private IStateContainer StateContainer { get; set; } = default!; [Inject] private IStateContainer StateContainer { get; set; } = default!;
@ -28,12 +29,17 @@ public partial class Configure
[Inject] private IJSRuntime JSRuntime { get; set; } = default!; [Inject] private IJSRuntime JSRuntime { get; set; } = default!;
private ModList? Modlist => StateContainer.Modlist; private ModList? Modlist => StateContainer.Modlist;
private string ModlistImage => StateContainer.ModlistImage;
private AbsolutePath ModlistPath => StateContainer.ModlistPath; private AbsolutePath ModlistPath => StateContainer.ModlistPath;
private AbsolutePath InstallPath { get; set; } private AbsolutePath InstallPath => StateContainer.InstallPath;
private AbsolutePath DownloadPath { get; set; } private AbsolutePath DownloadPath => StateContainer.DownloadPath;
public string StatusCategory { get; set; }
private string LastStatus { get; set; }
public List<string> StatusStep { get; set; } = new();
private string StatusText { get; set; } = string.Empty;
private InstallState InstallState => StateContainer.InstallState; private InstallState InstallState => StateContainer.InstallState;
private const string InstallSettingsPrefix = "install-settings-"; private const string InstallSettingsPrefix = "install-settings-";
@ -41,66 +47,13 @@ public partial class Configure
private bool _shouldRender; private bool _shouldRender;
protected override bool ShouldRender() => _shouldRender; protected override bool ShouldRender() => _shouldRender;
protected override async Task OnInitializedAsync() protected override void OnInitialized()
{ {
// var Location = KnownFolders.EntryPoint.Combine("downloaded_mod_lists", machineURL).WithExtension(Ext.Wabbajack); Install();
await CheckValidInstallPath();
_shouldRender = true; _shouldRender = true;
} }
private async Task CheckValidInstallPath() private async void Install()
{
if (ModlistPath == AbsolutePath.Empty) return;
var modlist = await StandardInstaller.LoadFromFile(DTOs, ModlistPath);
StateContainer.Modlist = modlist;
var hex = (await ModlistPath.ToString().Hash()).ToHex();
var prevSettings = await SettingsManager.Load<SavedInstallSettings>(InstallSettingsPrefix + hex);
if (prevSettings.ModlistLocation == ModlistPath)
{
StateContainer.ModlistPath = prevSettings.ModlistLocation;
InstallPath = prevSettings.InstallLocation;
DownloadPath = prevSettings.DownloadLocation;
//ModlistMetadata = metadata ?? prevSettings.Metadata;
}
// 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
{
var installPath = await Dialog.ShowDialogNonBlocking(true);
if (installPath is not null) InstallPath = (AbsolutePath)installPath;
}
catch (Exception e)
{
Logger.LogError(e, "Exception selecting install folder");
}
}
private async void SelectDownloadFolder()
{
try
{
var downloadPath = await Dialog.ShowDialogNonBlocking(true);
if (downloadPath is not null) DownloadPath = (AbsolutePath)downloadPath;
}
catch (Exception e)
{
Logger.LogError(e, "Exception selecting download folder");
}
}
private async Task Install()
{ {
if (Modlist is null) return; if (Modlist is null) return;
@ -133,9 +86,11 @@ public partial class Configure
installer.OnStatusUpdate = update => installer.OnStatusUpdate = update =>
{ {
var (statusText, _, _) = update; if (LastStatus == update.StatusText) return;
if (StatusText == statusText) return; StatusStep.Insert(0, update.StatusText);
StatusText = statusText; StatusCategory = update.StatusCategory;
LastStatus = update.StatusText;
InvokeAsync(StateHasChanged);
}; };
await installer.Begin(CancellationToken.None); await installer.Begin(CancellationToken.None);
@ -148,11 +103,3 @@ public partial class Configure
} }
} }
} }
internal class SavedInstallSettings
{
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; }
}

View File

@ -0,0 +1,115 @@
@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;
@mixin path-span {
display: block;
height: 2rem;
padding: 0.25rem;
margin: 0.25rem;
white-space: pre;
cursor: pointer;
text-overflow: ellipsis;
overflow: hidden;
}
#content {
display: flex;
height: 100%;
align-content: center;
justify-content: space-around;
align-items: center;
color: white;
flex-direction: column;
.install-background {
position: absolute;
width: calc(100% - #{$sidebar-width});
height: calc(100% - #{$header-height});
filter: blur(25px) brightness(50%);
z-index: -1;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.list {
display: flex;
flex: 1;
overflow: hidden;
align-items: center;
.left-side, .right-side {
flex: 1;
margin: 1rem;
}
}
.logger-container {
height: 200px;
width: 100%;
padding: 0.5rem;
background: rgba(0, 0, 0, 0.2);
color: lightgrey;
border: solid 1px black;
}
.settings {
font-size: 0.85rem;
display: flex;
align-items: center;
width: 100%;
padding: 1rem;
backdrop-filter: brightness(0.5);
.locations {
display: flex;
flex-direction: row;
flex: 1;
overflow: hidden;
.labels {
span {
@include path-span;
}
}
.paths {
flex: 1;
margin-left: 1rem;
overflow: hidden;
span {
@include path-span;
border: solid 1px rgba(255, 255, 255, 0.3);
}
}
}
.options {
display: flex;
flex-flow: row wrap;
flex-direction: column;
margin-left: 2rem;
}
.install {
display: flex;
flex-direction: column;
align-items: center;
margin: 0.5rem;
cursor: pointer;
img {
width: 5rem;
height: 5rem;
}
}
}
}

View File

@ -1,4 +1,4 @@
@page "/install" @page "/install/select"
@namespace Wabbajack.App.Blazor.Pages @namespace Wabbajack.App.Blazor.Pages
@ -26,5 +26,5 @@
</div> </div>
@code { @code {
public const string Route = "/install"; public const string Route = "/install/select";
} }

View File

@ -6,7 +6,7 @@ using Wabbajack.Paths;
namespace Wabbajack.App.Blazor.Pages; namespace Wabbajack.App.Blazor.Pages;
public partial class Install public partial class Select
{ {
[Inject] private NavigationManager NavigationManager { get; set; } = default!; [Inject] private NavigationManager NavigationManager { get; set; } = default!;
[Inject] private IStateContainer StateContainer { get; set; } = default!; [Inject] private IStateContainer StateContainer { get; set; } = default!;
@ -23,6 +23,4 @@ public partial class Install
NavigationManager.NavigateTo(Configure.Route); NavigationManager.NavigateTo(Configure.Route);
} }
private void VerifyFile(AbsolutePath path) { }
} }

View File

@ -1,4 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@ -41,6 +41,12 @@
const blob = new Blob([arrayBuffer]); const blob = new Blob([arrayBuffer]);
document.getElementById(imageElementId).src = URL.createObjectURL(blob); document.getElementById(imageElementId).src = URL.createObjectURL(blob);
} }
async function getBlobUrlFromStream(imageStream) {
const arrayBuffer = await imageStream.arrayBuffer();
const blob = new Blob([arrayBuffer]);
return URL.createObjectURL(blob);
}
</script> </script>
</body> </body>
</html> </html>