mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Merge pull request #1847 from Unnoen/more-blazor
Blazor updates and minor changes to other internal projects.
This commit is contained in:
commit
a725688495
@ -8,6 +8,8 @@ using NLog.Targets;
|
||||
using Wabbajack.App.Blazor.State;
|
||||
using Wabbajack.App.Blazor.Utility;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
using Blazored.Modal;
|
||||
using Blazored.Toast;
|
||||
|
||||
namespace Wabbajack.App.Blazor;
|
||||
|
||||
@ -22,6 +24,7 @@ public partial class App
|
||||
.ConfigureServices(services => ConfigureServices(services))
|
||||
.Build()
|
||||
.Services;
|
||||
_serviceProvider.GetRequiredService<SystemParametersConstructor>();
|
||||
}
|
||||
|
||||
private static void SetupLogging(ILoggingBuilder loggingBuilder)
|
||||
@ -30,30 +33,43 @@ public partial class App
|
||||
|
||||
var fileTarget = new FileTarget("file")
|
||||
{
|
||||
FileName = "log.log"
|
||||
FileName = "logs/Wabbajack.current.log",
|
||||
ArchiveFileName = "logs/Wabbajack.{##}.log",
|
||||
ArchiveOldFileOnStartup = true,
|
||||
MaxArchiveFiles = 10,
|
||||
Layout = "${processtime} [${level:uppercase=true}] (${logger}) ${message:withexception=true}",
|
||||
Header = "############ Wabbajack log file - ${longdate} ############"
|
||||
};
|
||||
var consoleTarget = new ConsoleTarget("console");
|
||||
var uiTarget = new MemoryTarget("ui");
|
||||
var uiTarget = new MemoryTarget
|
||||
{
|
||||
Name = "ui",
|
||||
Layout = "${message}",
|
||||
|
||||
};
|
||||
|
||||
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.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.AddBlazoredModal();
|
||||
services.AddBlazoredToast();
|
||||
services.AddTransient<MainWindow>();
|
||||
services.AddSingleton<SystemParametersConstructor>();
|
||||
services.AddSingleton<IStateContainer, StateContainer>();
|
||||
|
9
Wabbajack.App.Blazor/Components/InfoModal.razor
Normal file
9
Wabbajack.App.Blazor/Components/InfoModal.razor
Normal file
@ -0,0 +1,9 @@
|
||||
@namespace Wabbajack.App.Blazor.Components
|
||||
|
||||
<h3>[TBI] Model Component</h3>
|
||||
<p>@Content</p>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string Content { get; set; }
|
||||
}
|
@ -1,18 +1,32 @@
|
||||
@using Wabbajack.RateLimiter
|
||||
@using System
|
||||
@using System.Reactive.Linq
|
||||
|
||||
@namespace Wabbajack.App.Blazor.Components
|
||||
|
||||
<div id="progress-bar">
|
||||
<progress value="@Percentage.Value"></progress>
|
||||
<progress value="@CurrentProgress"></progress>
|
||||
<span class="text">@Text</span>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public Percent Percentage { get; set; }
|
||||
private double CurrentProgress { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Text { get; set; }
|
||||
[Parameter] public IObservable<Percent> ProgressObserver { get; set; }
|
||||
|
||||
[Parameter] public string? Text { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var textPercentage = string.IsNullOrEmpty(Text);
|
||||
ProgressObserver
|
||||
.Sample(TimeSpan.FromMilliseconds(250))
|
||||
.DistinctUntilChanged(p => p.Value)
|
||||
.Subscribe(p => {
|
||||
CurrentProgress = p.Value;
|
||||
if (textPercentage) Text = p.ToString();
|
||||
InvokeAsync(StateHasChanged);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@
|
||||
{
|
||||
{"Play", Play.Route},
|
||||
{"Gallery", Gallery.Route},
|
||||
{"Install", Install.Route},
|
||||
{"Install", Select.Route},
|
||||
{"Create", Create.Route}
|
||||
};
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
<Fluxor.Blazor.Web.StoreInitializer/>
|
||||
|
||||
@using Wabbajack.App.Blazor.Shared
|
||||
|
||||
<Router AppAssembly="@GetType().Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||
</Found>
|
||||
<NotFound>
|
||||
<h1>Not found</h1>
|
||||
<p>Sorry, there's nothing here.</p>
|
||||
</NotFound>
|
||||
</Router>
|
||||
<CascadingBlazoredModal>
|
||||
<Router AppAssembly="@GetType().Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||
</Found>
|
||||
<NotFound>
|
||||
<h1>Not found</h1>
|
||||
<p>Sorry, there's nothing here.</p>
|
||||
</NotFound>
|
||||
</Router>
|
||||
</CascadingBlazoredModal>
|
||||
|
@ -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";
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
@page "/gallery"
|
||||
|
||||
@using System.Globalization
|
||||
|
||||
@namespace Wabbajack.App.Blazor.Pages
|
||||
|
||||
<div id="content">
|
||||
@ -39,7 +37,7 @@
|
||||
{
|
||||
<BottomBar Image="@DownloadingMetaData.Links.ImageUri" Title="Downloading..." Subtitle="@DownloadingMetaData.Title">
|
||||
<div style="height:1.5rem;">
|
||||
<ProgressBar Percentage="@DownloadProgress" Text="@DownloadProgress.Value.ToString(CultureInfo.InvariantCulture)"/>
|
||||
<ProgressBar ProgressObserver="@DownloadProgress"/>
|
||||
</div>
|
||||
</BottomBar>
|
||||
}
|
||||
|
@ -4,12 +4,13 @@ using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Shell;
|
||||
using Blazored.Modal;
|
||||
using Blazored.Modal.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Wabbajack.App.Blazor.Components;
|
||||
using Wabbajack.App.Blazor.State;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.Paths.IO;
|
||||
using Wabbajack.RateLimiter;
|
||||
using Wabbajack.Services.OSIntegrated.Services;
|
||||
|
||||
@ -22,13 +23,14 @@ public partial class Gallery
|
||||
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
||||
[Inject] private ModListDownloadMaintainer Maintainer { get; set; } = default!;
|
||||
|
||||
private Percent DownloadProgress { get; set; } = Percent.Zero;
|
||||
[Inject] private IModalService Modal { get; set; } = default!;
|
||||
private IObservable<Percent> DownloadProgress { get; set; }
|
||||
private ModlistMetadata? DownloadingMetaData { get; set; }
|
||||
|
||||
private IEnumerable<ModlistMetadata> Modlists => StateContainer.Modlists;
|
||||
|
||||
private bool _errorLoadingModlists;
|
||||
|
||||
|
||||
private bool _shouldRender;
|
||||
protected override bool ShouldRender() => _shouldRender;
|
||||
|
||||
@ -44,19 +46,24 @@ public partial class Gallery
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_shouldRender = true;
|
||||
}
|
||||
|
||||
private async Task OnClickDownload(ModlistMetadata metadata)
|
||||
{
|
||||
// GlobalState.NavigationAllowed = !GlobalState.NavigationAllowed;
|
||||
await Download(metadata);
|
||||
if (!await Maintainer.HaveModList(metadata)) await Download(metadata);
|
||||
StateContainer.ModlistPath = Maintainer.ModListPath(metadata);
|
||||
StateContainer.Modlist = null;
|
||||
NavigationManager.NavigateTo(Configure.Route);
|
||||
}
|
||||
|
||||
private async Task OnClickInformation(ModlistMetadata metadata)
|
||||
{
|
||||
// TODO: [High] Implement information modal.
|
||||
var parameters = new ModalParameters();
|
||||
parameters.Add(nameof(InfoModal.Content), metadata.Description);
|
||||
Modal.Show<InfoModal>("Information", parameters);
|
||||
}
|
||||
|
||||
private async Task Download(ModlistMetadata metadata)
|
||||
@ -64,35 +71,26 @@ public partial class Gallery
|
||||
StateContainer.NavigationAllowed = false;
|
||||
DownloadingMetaData = metadata;
|
||||
|
||||
// TODO: download progress should be in ProgressBar component so it can refresh independently
|
||||
|
||||
try
|
||||
{
|
||||
var (progress, task) = Maintainer.DownloadModlist(metadata);
|
||||
|
||||
var dispose = progress
|
||||
.DistinctUntilChanged(p => p.Value)
|
||||
.Throttle(TimeSpan.FromMilliseconds(100))
|
||||
.Subscribe(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(); });
|
||||
DownloadProgress = progress;
|
||||
|
||||
var dispose = progress
|
||||
.Sample(TimeSpan.FromMilliseconds(250))
|
||||
.DistinctUntilChanged(p => p.Value)
|
||||
.Subscribe(p => {
|
||||
StateContainer.TaskBarState = new TaskBarState
|
||||
{
|
||||
Description = $"Downloading {metadata.Title}",
|
||||
State = TaskbarItemProgressState.Normal,
|
||||
ProgressValue = p.Value
|
||||
};
|
||||
}, () => { StateContainer.TaskBarState = new TaskBarState(); });
|
||||
|
||||
await task;
|
||||
dispose.Dispose();
|
||||
|
||||
var path = KnownFolders.EntryPoint.Combine("downloaded_mod_lists", metadata.Links.MachineURL).WithExtension(Ext.Wabbajack);
|
||||
StateContainer.ModlistPath = path;
|
||||
NavigationManager.NavigateTo(Configure.Route);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
54
Wabbajack.App.Blazor/Pages/Install/Configure.razor
Normal file
54
Wabbajack.App.Blazor/Pages/Install/Configure.razor
Normal file
@ -0,0 +1,54 @@
|
||||
@page "/install/configure"
|
||||
|
||||
@namespace Wabbajack.App.Blazor.Pages
|
||||
|
||||
<div id="content">
|
||||
<div class="install-background">
|
||||
@if (ModlistImage is not null)
|
||||
{
|
||||
<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">
|
||||
@if (ModlistImage is not null)
|
||||
{
|
||||
<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";
|
||||
}
|
131
Wabbajack.App.Blazor/Pages/Install/Configure.razor.cs
Normal file
131
Wabbajack.App.Blazor/Pages/Install/Configure.razor.cs
Normal 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 = 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 Task 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 Task 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; }
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
@import "../Shared/Globals.scss";
|
||||
@import "../../Shared/Globals.scss";
|
||||
|
||||
$checkbox-background: rgba(255, 255, 255, 0.2);
|
||||
$checkbox-background-hover: darkgrey;
|
33
Wabbajack.App.Blazor/Pages/Install/Installing.razor
Normal file
33
Wabbajack.App.Blazor/Pages/Install/Installing.razor
Normal file
@ -0,0 +1,33 @@
|
||||
@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">
|
||||
<InfoBlock Supertitle="@StatusCategory" Title="@Modlist.Name"/>
|
||||
<div class="step-logger">
|
||||
@foreach (var step in StatusStep.Take(3))
|
||||
{
|
||||
<div class="step">@step</div>
|
||||
}
|
||||
</div>
|
||||
</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>
|
||||
}
|
||||
</div>
|
||||
<div class="logger-container">
|
||||
<VirtualLogger/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
public const string Route = "/install/installing";
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Wabbajack.DTOs;
|
||||
@ -16,7 +17,7 @@ using Wabbajack.App.Blazor.State;
|
||||
|
||||
namespace Wabbajack.App.Blazor.Pages;
|
||||
|
||||
public partial class Configure
|
||||
public partial class Installing
|
||||
{
|
||||
[Inject] private ILogger<Configure> Logger { get; set; } = default!;
|
||||
[Inject] private IStateContainer StateContainer { get; set; } = default!;
|
||||
@ -26,14 +27,19 @@ public partial class Configure
|
||||
[Inject] private IGameLocator GameLocator { get; set; } = default!;
|
||||
[Inject] private SettingsManager SettingsManager { get; set; } = default!;
|
||||
[Inject] private IJSRuntime JSRuntime { get; set; } = default!;
|
||||
|
||||
|
||||
private ModList? Modlist => StateContainer.Modlist;
|
||||
|
||||
private string ModlistImage => StateContainer.ModlistImage;
|
||||
private AbsolutePath ModlistPath => StateContainer.ModlistPath;
|
||||
private AbsolutePath InstallPath { get; set; }
|
||||
private AbsolutePath DownloadPath { get; set; }
|
||||
private AbsolutePath InstallPath => StateContainer.InstallPath;
|
||||
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 const string InstallSettingsPrefix = "install-settings-";
|
||||
@ -41,69 +47,16 @@ public partial class Configure
|
||||
private bool _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);
|
||||
|
||||
await CheckValidInstallPath();
|
||||
Install();
|
||||
_shouldRender = true;
|
||||
}
|
||||
|
||||
private async Task CheckValidInstallPath()
|
||||
{
|
||||
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;
|
||||
|
||||
|
||||
StateContainer.InstallState = InstallState.Installing;
|
||||
await Task.Run(() => BeginInstall(Modlist));
|
||||
}
|
||||
@ -130,12 +83,14 @@ public partial class Configure
|
||||
SystemParameters = ParametersConstructor.Create(),
|
||||
GameFolder = GameLocator.GameLocation(modlist.GameType)
|
||||
});
|
||||
|
||||
|
||||
installer.OnStatusUpdate = update =>
|
||||
{
|
||||
var (statusText, _, _) = update;
|
||||
if (StatusText == statusText) return;
|
||||
StatusText = statusText;
|
||||
if (LastStatus == update.StatusText) return;
|
||||
StatusStep.Insert(0, update.StatusText);
|
||||
StatusCategory = update.StatusCategory;
|
||||
LastStatus = update.StatusText;
|
||||
InvokeAsync(StateHasChanged);
|
||||
};
|
||||
|
||||
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; }
|
||||
}
|
149
Wabbajack.App.Blazor/Pages/Install/Installing.razor.scss
Normal file
149
Wabbajack.App.Blazor/Pages/Install/Installing.razor.scss
Normal file
@ -0,0 +1,149 @@
|
||||
@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;
|
||||
|
||||
.step-logger {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
|
||||
.step {
|
||||
&:nth-child(1) {
|
||||
margin-left: 0.5rem;
|
||||
font-size: 2rem;
|
||||
font-weight: 100;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
margin-left: 0.75rem;
|
||||
font-size: 1.85rem;
|
||||
font-weight: 100;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
filter: blur(1px);
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
margin-left: 1rem;
|
||||
font-size: 1.7rem;
|
||||
font-weight: 100;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
filter: blur(1.5px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
@page "/install"
|
||||
@page "/install/select"
|
||||
|
||||
@namespace Wabbajack.App.Blazor.Pages
|
||||
|
||||
@ -26,5 +26,5 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
public const string Route = "/install";
|
||||
public const string Route = "/install/select";
|
||||
}
|
@ -6,7 +6,7 @@ using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.App.Blazor.Pages;
|
||||
|
||||
public partial class Install
|
||||
public partial class Select
|
||||
{
|
||||
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
||||
[Inject] private IStateContainer StateContainer { get; set; } = default!;
|
||||
@ -23,6 +23,4 @@ public partial class Install
|
||||
|
||||
NavigationManager.NavigateTo(Configure.Route);
|
||||
}
|
||||
|
||||
private void VerifyFile(AbsolutePath path) { }
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
@inherits LayoutComponentBase
|
||||
@using Blazored.Toast.Configuration
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
@namespace Wabbajack.App.Blazor.Shared
|
||||
|
||||
<BlazoredToasts Position="ToastPosition.BottomRight"/>
|
||||
<div id="background"></div>
|
||||
<SideBar/>
|
||||
<div id="wrapper">
|
||||
|
@ -17,9 +17,18 @@ public interface IStateContainer
|
||||
IObservable<AbsolutePath> ModlistPathObservable { get; }
|
||||
AbsolutePath ModlistPath { get; set; }
|
||||
|
||||
IObservable<AbsolutePath> InstallPathObservable { get; }
|
||||
AbsolutePath InstallPath { get; set; }
|
||||
|
||||
IObservable<AbsolutePath> DownloadPathObservable { get; }
|
||||
AbsolutePath DownloadPath { get; set; }
|
||||
|
||||
IObservable<ModList?> ModlistObservable { get; }
|
||||
ModList? Modlist { get; set; }
|
||||
|
||||
IObservable<string?> ModlistImageObservable { get; }
|
||||
string? ModlistImage { get; set; }
|
||||
|
||||
IObservable<InstallState> InstallStateObservable { get; }
|
||||
InstallState InstallState { get; set; }
|
||||
|
||||
|
@ -55,6 +55,22 @@ public class StateContainer : IStateContainer
|
||||
set => _modlistPathObservable.Value = value;
|
||||
}
|
||||
|
||||
private readonly CustomObservable<AbsolutePath> _installPathObservable = new(AbsolutePath.Empty);
|
||||
public IObservable<AbsolutePath> InstallPathObservable => _installPathObservable;
|
||||
public AbsolutePath InstallPath
|
||||
{
|
||||
get => _installPathObservable.Value;
|
||||
set => _installPathObservable.Value = value;
|
||||
}
|
||||
|
||||
private readonly CustomObservable<AbsolutePath> _downloadPathObservable = new(AbsolutePath.Empty);
|
||||
public IObservable<AbsolutePath> DownloadPathObservable => _downloadPathObservable;
|
||||
public AbsolutePath DownloadPath
|
||||
{
|
||||
get => _downloadPathObservable.Value;
|
||||
set => _downloadPathObservable.Value = value;
|
||||
}
|
||||
|
||||
private readonly CustomObservable<ModList?> _modlistObservable = new(null);
|
||||
public IObservable<ModList?> ModlistObservable => _modlistObservable;
|
||||
public ModList? Modlist
|
||||
@ -62,6 +78,14 @@ public class StateContainer : IStateContainer
|
||||
get => _modlistObservable.Value;
|
||||
set => _modlistObservable.Value = value;
|
||||
}
|
||||
|
||||
private readonly CustomObservable<string?> _modlistImageObservable = new(string.Empty);
|
||||
public IObservable<string?> ModlistImageObservable => _modlistImageObservable;
|
||||
public string? ModlistImage
|
||||
{
|
||||
get => _modlistImageObservable.Value;
|
||||
set => _modlistImageObservable.Value = value;
|
||||
}
|
||||
|
||||
private readonly CustomObservable<InstallState> _installStateObservable = new(InstallState.Waiting);
|
||||
public IObservable<InstallState> InstallStateObservable => _installStateObservable;
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
@ -14,6 +14,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blazored.Modal" Version="6.0.1" />
|
||||
<PackageReference Include="Blazored.Toast" Version="3.2.2" />
|
||||
<PackageReference Include="DynamicData" Version="7.4.9" />
|
||||
<PackageReference Include="GitInfo" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.4" />
|
||||
|
@ -5,4 +5,8 @@
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using Wabbajack.App.Blazor.Components
|
||||
@using Wabbajack.App.Blazor.Components
|
||||
@using Blazored.Modal
|
||||
@using Blazored.Modal.Services
|
||||
@using Blazored.Toast
|
||||
@using Blazored.Toast.Services
|
||||
|
@ -7,6 +7,8 @@
|
||||
<title>Wabbajack</title>
|
||||
<base href="/"/>
|
||||
<link href="Wabbajack.App.Blazor.styles.css" rel="stylesheet" />
|
||||
<link href="_content/Blazored.Modal/blazored-modal.css" rel="stylesheet" />
|
||||
<link href="_content/Blazored.Toast/blazored-toast.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<style>
|
||||
@ -32,11 +34,12 @@
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="_framework/blazor.webview.js"></script>
|
||||
<script src="_content/Blazored.Modal/blazored.modal.js"></script>
|
||||
<script>
|
||||
async function setImageUsingStreaming(imageElementId, imageStream) {
|
||||
async function getBlobUrlFromStream(imageStream) {
|
||||
const arrayBuffer = await imageStream.arrayBuffer();
|
||||
const blob = new Blob([arrayBuffer]);
|
||||
document.getElementById(imageElementId).src = URL.createObjectURL(blob);
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
@ -51,6 +51,7 @@ public abstract class ACompiler
|
||||
|
||||
public ConcurrentDictionary<Directive, RawSourceFile> _sourceFileLinks;
|
||||
private string _statusText;
|
||||
private string _statusCategory;
|
||||
public List<IndexedArchive> IndexedArchives = new();
|
||||
|
||||
public Dictionary<Hash, IEnumerable<VirtualFile>> IndexedFiles = new();
|
||||
@ -106,16 +107,17 @@ public abstract class ACompiler
|
||||
|
||||
public event EventHandler<StatusUpdate> OnStatusUpdate;
|
||||
|
||||
public void NextStep(string statusText, long maxStepProgress = 1)
|
||||
public void NextStep(string statusCategory, string statusText, long maxStepProgress = 1)
|
||||
{
|
||||
_updateStopWatch.Restart();
|
||||
_maxStepProgress = maxStepProgress;
|
||||
_currentStep += 1;
|
||||
_statusText = statusText;
|
||||
_statusCategory = statusCategory;
|
||||
_logger.LogInformation("Compiler Step: {Step}", statusText);
|
||||
|
||||
if (OnStatusUpdate != null)
|
||||
OnStatusUpdate(this, new StatusUpdate($"[{_currentStep}/{MaxSteps}] " + statusText,
|
||||
OnStatusUpdate(this, new StatusUpdate(statusCategory, $"[{_currentStep}/{MaxSteps}] " + statusText,
|
||||
Percent.FactoryPutInRange(_currentStep, MaxSteps),
|
||||
Percent.Zero));
|
||||
}
|
||||
@ -131,7 +133,7 @@ public abstract class ACompiler
|
||||
}
|
||||
|
||||
if (OnStatusUpdate != null)
|
||||
OnStatusUpdate(this, new StatusUpdate(_statusText, Percent.FactoryPutInRange(_currentStep, MaxSteps),
|
||||
OnStatusUpdate(this, new StatusUpdate(_statusCategory, _statusText, Percent.FactoryPutInRange(_currentStep, MaxSteps),
|
||||
Percent.FactoryPutInRange(_currentStepProgress, _maxStepProgress)));
|
||||
}
|
||||
|
||||
@ -195,7 +197,7 @@ public abstract class ACompiler
|
||||
public async Task<bool> GatherMetaData()
|
||||
{
|
||||
_logger.LogInformation("Getting meta data for {count} archives", SelectedArchives.Count);
|
||||
NextStep("Gathering Metadata", SelectedArchives.Count);
|
||||
NextStep("Building", "Gathering Metadata", SelectedArchives.Count);
|
||||
await SelectedArchives.PDoAll(CompilerLimiter, async a =>
|
||||
{
|
||||
UpdateProgress(1);
|
||||
@ -208,7 +210,7 @@ public abstract class ACompiler
|
||||
|
||||
protected async Task IndexGameFileHashes()
|
||||
{
|
||||
NextStep("Indexing Game Files");
|
||||
NextStep("Compiling", "Indexing Game Files");
|
||||
if (_settings.UseGamePaths)
|
||||
{
|
||||
//taking the games in Settings.IncludedGames + currently compiling game so you can eg
|
||||
@ -258,7 +260,7 @@ public abstract class ACompiler
|
||||
|
||||
protected async Task CleanInvalidArchivesAndFillState()
|
||||
{
|
||||
NextStep("Cleaning Invalid Archives");
|
||||
NextStep("Compiling", "Cleaning Invalid Archives");
|
||||
var remove = await IndexedArchives.PMapAll(CompilerLimiter, async a =>
|
||||
{
|
||||
try
|
||||
@ -313,7 +315,7 @@ public abstract class ACompiler
|
||||
.Where(f => f.FileExists())
|
||||
.ToList();
|
||||
|
||||
NextStep("InferMetas", toFind.Count);
|
||||
NextStep("Initializing", "InferMetas", toFind.Count);
|
||||
if (toFind.Count == 0) return;
|
||||
|
||||
_logger.LogInformation("Attempting to infer {count} metas from the server.", toFind.Count);
|
||||
@ -353,7 +355,7 @@ public abstract class ACompiler
|
||||
|
||||
protected async Task ExportModList(CancellationToken token)
|
||||
{
|
||||
NextStep("Exporting Modlist");
|
||||
NextStep("Finalizing", "Exporting Modlist");
|
||||
_logger.LogInformation("Exporting ModList to {location}", _settings.OutputFile);
|
||||
|
||||
// Modify readme and ModList image to relative paths if they exist
|
||||
@ -431,7 +433,7 @@ public abstract class ACompiler
|
||||
}))
|
||||
.ToArray();
|
||||
|
||||
NextStep("Generating Patches", toBuild.Length);
|
||||
NextStep("Compiling","Generating Patches", toBuild.Length);
|
||||
if (toBuild.Length == 0) return;
|
||||
|
||||
// Extract all the source files
|
||||
@ -512,7 +514,7 @@ public abstract class ACompiler
|
||||
|
||||
public async Task GenerateManifest()
|
||||
{
|
||||
NextStep("Generating Manifest");
|
||||
NextStep("Finalizing", "Generating Manifest");
|
||||
var manifest = new Manifest(ModList);
|
||||
await using var of = _settings.OutputFile.Open(FileMode.Create, FileAccess.Write);
|
||||
await _dtos.Serialize(manifest, of);
|
||||
@ -520,7 +522,7 @@ public abstract class ACompiler
|
||||
|
||||
public async Task GatherArchives()
|
||||
{
|
||||
NextStep("Gathering Archives");
|
||||
NextStep("Building", "Gathering Archives");
|
||||
_logger.LogInformation("Building a list of archives based on the files required");
|
||||
|
||||
var hashes = InstallDirectives.OfType<FromArchive>()
|
||||
@ -621,7 +623,7 @@ public abstract class ACompiler
|
||||
.GroupBy(f => _sourceFileLinks[f].File)
|
||||
.ToDictionary(k => k.Key);
|
||||
|
||||
NextStep("Inlining Files");
|
||||
NextStep("Building", "Inlining Files");
|
||||
if (grouped.Count == 0) return;
|
||||
await _vfs.Extract(grouped.Keys.ToHashSet(), async (vf, sfn) =>
|
||||
{
|
||||
|
@ -59,12 +59,12 @@ public class MO2Compiler : ACompiler
|
||||
var roots = new List<AbsolutePath> {Settings.Source, Settings.Downloads};
|
||||
roots.AddRange(Settings.OtherGames.Append(Settings.Game).Select(g => _locator.GameLocation(g)));
|
||||
|
||||
NextStep("Add Roots");
|
||||
NextStep("Initializing", "Add Roots");
|
||||
await _vfs.AddRoots(roots, token); // Step 1
|
||||
|
||||
await InferMetas(token); // Step 2
|
||||
|
||||
NextStep("Add Download Roots");
|
||||
NextStep("Initializing", "Add Download Roots");
|
||||
await _vfs.AddRoot(Settings.Downloads, token); // Step 3
|
||||
|
||||
// Find all Downloads
|
||||
@ -125,14 +125,14 @@ public class MO2Compiler : ACompiler
|
||||
|
||||
var stack = MakeStack();
|
||||
|
||||
NextStep("Running Compilation Stack", AllFiles.Count);
|
||||
NextStep("Compiling", "Running Compilation Stack", AllFiles.Count);
|
||||
var results = await AllFiles.PMapAll(CompilerLimiter, f =>
|
||||
{
|
||||
UpdateProgress(1);
|
||||
return RunStack(stack, f);
|
||||
}).ToList();
|
||||
|
||||
NextStep("Updating Extra files");
|
||||
NextStep("Compiling", "Updating Extra files");
|
||||
// Add the extra files that were generated by the stack
|
||||
results = results.Concat(ExtraFiles).ToList();
|
||||
|
||||
@ -184,7 +184,7 @@ public class MO2Compiler : ACompiler
|
||||
|
||||
private async Task RunValidation(ModList modList)
|
||||
{
|
||||
NextStep("Validating Archives", modList.Archives.Length);
|
||||
NextStep("Finalizing", "Validating Archives", modList.Archives.Length);
|
||||
var allowList = await _wjClient.LoadDownloadAllowList();
|
||||
foreach (var archive in modList.Archives)
|
||||
{
|
||||
|
@ -26,7 +26,7 @@ using Wabbajack.VFS;
|
||||
|
||||
namespace Wabbajack.Installer;
|
||||
|
||||
public record StatusUpdate(string StatusText, Percent StepsProgress, Percent StepProgress)
|
||||
public record StatusUpdate(string StatusCategory, string StatusText, Percent StepsProgress, Percent StepProgress)
|
||||
{
|
||||
}
|
||||
|
||||
@ -57,6 +57,7 @@ public abstract class AInstaller<T>
|
||||
|
||||
|
||||
protected long MaxStepProgress { get; set; }
|
||||
private string _statusCategory;
|
||||
private string _statusText;
|
||||
private readonly Stopwatch _updateStopWatch = new();
|
||||
|
||||
@ -92,15 +93,16 @@ public abstract class AInstaller<T>
|
||||
|
||||
public ModList ModList => _configuration.ModList;
|
||||
|
||||
public void NextStep(string statusText, long maxStepProgress)
|
||||
public void NextStep(string statusCategory, string statusText, long maxStepProgress)
|
||||
{
|
||||
_updateStopWatch.Restart();
|
||||
MaxStepProgress = maxStepProgress;
|
||||
_currentStep += 1;
|
||||
_statusText = statusText;
|
||||
_statusCategory = statusCategory;
|
||||
_logger.LogInformation("Next Step: {Step}", statusText);
|
||||
|
||||
OnStatusUpdate?.Invoke(new StatusUpdate($"[{_currentStep}/{MaxSteps}] " + statusText,
|
||||
OnStatusUpdate?.Invoke(new StatusUpdate(statusCategory, statusText,
|
||||
Percent.FactoryPutInRange(_currentStep, MaxSteps), Percent.Zero));
|
||||
}
|
||||
|
||||
@ -108,8 +110,7 @@ public abstract class AInstaller<T>
|
||||
{
|
||||
Interlocked.Add(ref _currentStepProgress, stepProgress);
|
||||
|
||||
OnStatusUpdate?.Invoke(new StatusUpdate($"[{_currentStep}/{MaxSteps}] " + _statusText, Percent.FactoryPutInRange(_currentStep, MaxSteps),
|
||||
Percent.FactoryPutInRange(_currentStepProgress, MaxStepProgress)));
|
||||
OnStatusUpdate?.Invoke(new StatusUpdate(_statusCategory, _statusText, Percent.FactoryPutInRange(_currentStep, MaxSteps), Percent.FactoryPutInRange(_currentStepProgress, MaxStepProgress)));
|
||||
}
|
||||
|
||||
public abstract Task<bool> Begin(CancellationToken token);
|
||||
@ -119,7 +120,7 @@ public abstract class AInstaller<T>
|
||||
ExtractedModlistFolder = _manager.CreateFolder();
|
||||
await using var stream = _configuration.ModlistArchive.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using var archive = new ZipArchive(stream, ZipArchiveMode.Read);
|
||||
NextStep("Extracting Modlist", archive.Entries.Count);
|
||||
NextStep("Preparing","Extracting Modlist", archive.Entries.Count);
|
||||
foreach (var entry in archive.Entries)
|
||||
{
|
||||
var path = entry.FullName.ToRelativePath().RelativeTo(ExtractedModlistFolder);
|
||||
@ -181,7 +182,7 @@ public abstract class AInstaller<T>
|
||||
/// </summary>
|
||||
protected async Task PrimeVFS()
|
||||
{
|
||||
NextStep("Priming VFS", 0);
|
||||
NextStep("Preparing","Priming VFS", 0);
|
||||
_vfs.AddKnown(_configuration.ModList.Directives.OfType<FromArchive>().Select(d => d.ArchiveHashPath),
|
||||
HashedArchives);
|
||||
await _vfs.BackfillMissing();
|
||||
@ -189,7 +190,7 @@ public abstract class AInstaller<T>
|
||||
|
||||
public async Task BuildFolderStructure()
|
||||
{
|
||||
NextStep("Building Folder Structure", 0);
|
||||
NextStep("Preparing", "Building Folder Structure", 0);
|
||||
_logger.LogInformation("Building Folder Structure");
|
||||
ModList.Directives
|
||||
.Where(d => d.To.Depth > 1)
|
||||
@ -200,7 +201,7 @@ public abstract class AInstaller<T>
|
||||
|
||||
public async Task InstallArchives(CancellationToken token)
|
||||
{
|
||||
NextStep("Installing files", ModList.Directives.Sum(d => d.Size));
|
||||
NextStep("Installing", "Installing files", ModList.Directives.Sum(d => d.Size));
|
||||
var grouped = ModList.Directives
|
||||
.OfType<FromArchive>()
|
||||
.Select(a => new {VF = _vfs.Index.FileForArchiveHashPath(a.ArchiveHashPath), Directive = a})
|
||||
@ -302,7 +303,7 @@ public abstract class AInstaller<T>
|
||||
}
|
||||
|
||||
_logger.LogInformation("Downloading {count} archives", missing.Count);
|
||||
NextStep("Downloading files", missing.Count);
|
||||
NextStep("Downloading", "Downloading files", missing.Count);
|
||||
|
||||
await missing
|
||||
.OrderBy(a => a.Size)
|
||||
@ -363,7 +364,7 @@ public abstract class AInstaller<T>
|
||||
|
||||
public async Task HashArchives(CancellationToken token)
|
||||
{
|
||||
NextStep("Hashing Archives", 0);
|
||||
NextStep("Hashing", "Hashing Archives", 0);
|
||||
_logger.LogInformation("Looking for files to hash");
|
||||
|
||||
var allFiles = _configuration.Downloads.EnumerateFiles()
|
||||
@ -414,7 +415,7 @@ public abstract class AInstaller<T>
|
||||
var savePath = (RelativePath) "saves";
|
||||
|
||||
var existingFiles = _configuration.Install.EnumerateFiles().ToList();
|
||||
NextStep("Optimizing Modlist: Looking for files to delete", existingFiles.Count);
|
||||
NextStep("Preparing", "Looking for files to delete", existingFiles.Count);
|
||||
await existingFiles
|
||||
.PDoAll(async f =>
|
||||
{
|
||||
@ -428,12 +429,12 @@ public abstract class AInstaller<T>
|
||||
if (NoDeleteRegex.IsMatch(f.ToString()))
|
||||
return;
|
||||
|
||||
_logger.LogInformation("Deleting {relativeTo} it's not part of this ModList", relativeTo);
|
||||
_logger.LogTrace("Deleting {relativeTo} it's not part of this ModList", relativeTo);
|
||||
f.Delete();
|
||||
});
|
||||
|
||||
_logger.LogInformation("Cleaning empty folders");
|
||||
NextStep("Optimizing Modlist: Cleaning empty folders", indexed.Keys.Count);
|
||||
NextStep("Preparing", "Cleaning empty folders", indexed.Keys.Count);
|
||||
var expectedFolders = (indexed.Keys
|
||||
.Select(f => f.RelativeTo(_configuration.Install))
|
||||
// We ignore the last part of the path, so we need a dummy file name
|
||||
@ -468,7 +469,7 @@ public abstract class AInstaller<T>
|
||||
|
||||
var existingfiles = _configuration.Install.EnumerateFiles().ToHashSet();
|
||||
|
||||
NextStep("Optimizing Modlist: Removing redundant directives", indexed.Count);
|
||||
NextStep("Preparing", "Removing redundant directives", indexed.Count);
|
||||
await indexed.Values.PMapAll<Directive, Directive?>(async d =>
|
||||
{
|
||||
// Bit backwards, but we want to return null for
|
||||
@ -487,7 +488,7 @@ public abstract class AInstaller<T>
|
||||
|
||||
_logger.LogInformation("Optimized {optimized} directives to {indexed} required", ModList.Directives.Length,
|
||||
indexed.Count);
|
||||
NextStep("Finalizing modlist optimization", 0);
|
||||
NextStep("Preparing", "Finalizing modlist optimization", 0);
|
||||
var requiredArchives = indexed.Values.OfType<FromArchive>()
|
||||
.GroupBy(d => d.ArchiveHashPath.Hash)
|
||||
.Select(d => d.Key)
|
||||
|
@ -4,6 +4,7 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using IniParser;
|
||||
@ -60,7 +61,7 @@ public class StandardInstaller : AInstaller<StandardInstaller>
|
||||
{
|
||||
if (token.IsCancellationRequested) return false;
|
||||
await _wjClient.SendMetric(MetricNames.BeginInstall, ModList.Name);
|
||||
NextStep("Configuring Installer", 0);
|
||||
NextStep("Preparing", "Configuring Installer", 0);
|
||||
_logger.LogInformation("Configuring Processor");
|
||||
|
||||
if (_configuration.GameFolder == default)
|
||||
@ -144,7 +145,7 @@ public class StandardInstaller : AInstaller<StandardInstaller>
|
||||
await ExtractedModlistFolder!.DisposeAsync();
|
||||
await _wjClient.SendMetric(MetricNames.FinishInstall, ModList.Name);
|
||||
|
||||
NextStep("Finished", 1);
|
||||
NextStep("Finished", "Finished", 1);
|
||||
_logger.LogInformation("Finished Installation");
|
||||
return true;
|
||||
}
|
||||
@ -274,7 +275,7 @@ public class StandardInstaller : AInstaller<StandardInstaller>
|
||||
private async Task InstallIncludedFiles(CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation("Writing inline files");
|
||||
NextStep("Installing Included Files", ModList.Directives.OfType<InlineFile>().Count());
|
||||
NextStep("Installing", "Installing Included Files", ModList.Directives.OfType<InlineFile>().Count());
|
||||
await ModList.Directives
|
||||
.OfType<InlineFile>()
|
||||
.PDoAll(async directive =>
|
||||
@ -301,6 +302,7 @@ public class StandardInstaller : AInstaller<StandardInstaller>
|
||||
_logger.LogWarning("No SystemParameters set, ignoring ini settings for system parameters");
|
||||
|
||||
var config = new IniParserConfiguration {AllowDuplicateKeys = true, AllowDuplicateSections = true};
|
||||
config.CommentRegex = new Regex(@"^(#|;)(.*)");
|
||||
var oblivionPath = (RelativePath) "Oblivion.ini";
|
||||
foreach (var file in _configuration.Install.Combine("profiles").EnumerateFiles()
|
||||
.Where(f => ((string) f.FileName).EndsWith("refs.ini") || f.FileName == oblivionPath))
|
||||
@ -327,8 +329,9 @@ public class StandardInstaller : AInstaller<StandardInstaller>
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (modified)
|
||||
parser.WriteFile(file.ToString(), data);
|
||||
if (!modified) continue;
|
||||
parser.WriteFile(file.ToString(), data);
|
||||
_logger.LogTrace("Remapped screen size in {file}", file);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user