1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
bin
|
||||
obj
|
||||
.idea
|
||||
.vs
|
||||
*.user
|
12
Wabbajack.App.Blazor/.config/dotnet-tools.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"excubo.webcompiler": {
|
||||
"version": "2.7.14",
|
||||
"commands": [
|
||||
"webcompiler"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
23
Wabbajack.App.Blazor/.editorconfig
Normal file
@ -0,0 +1,23 @@
|
||||
# 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
|
||||
|
||||
[*.scss]
|
||||
indent_size = 2
|
3
Wabbajack.App.Blazor/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.sonarqube
|
||||
**/*.css
|
||||
**/*.css.map
|
8
Wabbajack.App.Blazor/App.xaml
Normal file
@ -0,0 +1,8 @@
|
||||
<Application x:Class="Wabbajack.App.Blazor.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Startup="OnStartup"
|
||||
Exit="OnExit">
|
||||
<Application.Resources>
|
||||
</Application.Resources>
|
||||
</Application>
|
56
Wabbajack.App.Blazor/App.xaml.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Wabbajack.App.Blazor.Models;
|
||||
using Wabbajack.App.Blazor.State;
|
||||
using Wabbajack.App.Blazor.Utility;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
|
||||
namespace Wabbajack.App.Blazor;
|
||||
|
||||
public partial class App
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IHost _host;
|
||||
|
||||
public App()
|
||||
{
|
||||
_host = Host.CreateDefaultBuilder(Array.Empty<string>())
|
||||
.ConfigureLogging(c => { c.ClearProviders(); })
|
||||
.ConfigureServices((host, services) => { ConfigureServices(services); })
|
||||
.Build();
|
||||
|
||||
_serviceProvider = _host.Services;
|
||||
}
|
||||
|
||||
private static IServiceCollection ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddOSIntegrated();
|
||||
services.AddBlazorWebView();
|
||||
services.AddAllSingleton<ILoggerProvider, LoggerProvider>();
|
||||
services.AddTransient<MainWindow>();
|
||||
services.AddSingleton<SystemParametersConstructor>();
|
||||
services.AddSingleton<GlobalState>();
|
||||
return services;
|
||||
}
|
||||
|
||||
private void OnStartup(object sender, StartupEventArgs e)
|
||||
{
|
||||
var mainWindow = _serviceProvider.GetRequiredService<MainWindow>();
|
||||
mainWindow!.Show();
|
||||
}
|
||||
|
||||
private void OnExit(object sender, ExitEventArgs e)
|
||||
{
|
||||
Current.Shutdown();
|
||||
// using (_host)
|
||||
// {
|
||||
// _host.StopAsync();
|
||||
// }
|
||||
//
|
||||
// base.OnExit(e);
|
||||
}
|
||||
}
|
10
Wabbajack.App.Blazor/AssemblyInfo.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
30
Wabbajack.App.Blazor/Components/BottomBar.razor
Normal file
@ -0,0 +1,30 @@
|
||||
@namespace Wabbajack.App.Blazor.Components
|
||||
|
||||
<footer id="bottom-bar">
|
||||
<div class="image">
|
||||
<img src="@Image" alt="">
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="subtitle">@Subtitle</div>
|
||||
<div class="title">@Title</div>
|
||||
</div>
|
||||
<div class="inside-content">
|
||||
@ChildContent
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public string Title { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Subtitle { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Image { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
|
||||
}
|
43
Wabbajack.App.Blazor/Components/BottomBar.razor.scss
Normal file
@ -0,0 +1,43 @@
|
||||
@import "../Shared/Globals.scss";
|
||||
|
||||
#bottom-bar {
|
||||
position: fixed;
|
||||
width: calc(100% - #{$sidebar-width});
|
||||
height: $header-height;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(2px) saturate(0.25);
|
||||
z-index: 2;
|
||||
|
||||
//font-family: $raleway-font;
|
||||
text-transform: uppercase;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
color: white;
|
||||
|
||||
.image {
|
||||
width: auto;
|
||||
height: $header-height;
|
||||
margin-right: 1rem;
|
||||
padding: 0.25rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
.title {
|
||||
font-size: 1.5em;
|
||||
font-weight: 100;
|
||||
}
|
||||
}
|
||||
|
||||
.inside-content {
|
||||
flex: 1;
|
||||
margin: 5rem;
|
||||
}
|
||||
}
|
31
Wabbajack.App.Blazor/Components/InfoBlock.razor
Normal file
@ -0,0 +1,31 @@
|
||||
@namespace Wabbajack.App.Blazor.Components
|
||||
|
||||
<div id="info-block">
|
||||
@if (Supertitle != string.Empty)
|
||||
{
|
||||
<span class="supertitle">@Supertitle</span>
|
||||
}
|
||||
<span class="title">@Title</span>
|
||||
<span class="subtitle">@Subtitle</span>
|
||||
<span class="comment">@Comment</span>
|
||||
<span class="description">@Description</span>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public string Supertitle { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string Title { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Subtitle { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Comment { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Description { get; set; }
|
||||
|
||||
}
|
38
Wabbajack.App.Blazor/Components/InfoBlock.razor.scss
Normal file
@ -0,0 +1,38 @@
|
||||
#info-block {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
|
||||
.supertitle {
|
||||
margin-left: 0.5rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 4rem;
|
||||
font-weight: 100;
|
||||
margin-top: -1rem;
|
||||
margin-bottom: -0.5rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-left: 0.5rem;
|
||||
font-size: 2rem;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.comment {
|
||||
margin-left: 1rem;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-left: 1.5rem;
|
||||
margin-top: 0.5rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
37
Wabbajack.App.Blazor/Components/InfoImage.razor
Normal file
@ -0,0 +1,37 @@
|
||||
@namespace Wabbajack.App.Blazor.Components
|
||||
|
||||
<div id="info-image">
|
||||
<div class="image">
|
||||
<img src="@Image" alt="">
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(Title))
|
||||
{
|
||||
<span class="title">@Title</span>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(Subtitle))
|
||||
{
|
||||
<span class="subtitle">@Subtitle</span>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(Description))
|
||||
{
|
||||
<span class="description">@Description</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public string Image { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Title { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Subtitle { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Description { get; set; }
|
||||
|
||||
}
|
40
Wabbajack.App.Blazor/Components/InfoImage.razor.scss
Normal file
@ -0,0 +1,40 @@
|
||||
#info-image {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
|
||||
.mod-feature {
|
||||
margin-left: -10px;
|
||||
font-size: 2rem;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.image {
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2rem;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 100;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-top: 0.5rem;
|
||||
margin-left: 1rem;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
}
|
19
Wabbajack.App.Blazor/Components/InteractionIcon.razor
Normal file
@ -0,0 +1,19 @@
|
||||
@namespace Wabbajack.App.Blazor.Components
|
||||
|
||||
<img src="@Icon" class="interaction-icon" style="width: @Size; height: @Size; margin: 0;" alt="@Label" @onclick="OnClick">
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public string Icon { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Label { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Size { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<MouseEventArgs> OnClick { get; set; }
|
||||
|
||||
}
|
28
Wabbajack.App.Blazor/Components/ModlistItem.razor
Normal file
@ -0,0 +1,28 @@
|
||||
@using Wabbajack.DTOs
|
||||
|
||||
@namespace Wabbajack.App.Blazor.Components
|
||||
|
||||
<div class="item">
|
||||
<div class="display">
|
||||
<img src="@Metadata.Links.ImageUri" loading="lazy" class="image" alt="@Metadata.Title">
|
||||
<div class="interaction">
|
||||
@ChildContent
|
||||
</div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="title">@Metadata.Title</div>
|
||||
<div class="author">@Metadata.Author</div>
|
||||
<div class="description">@Metadata.Description</div>
|
||||
</div>
|
||||
<div class="tags"></div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public ModlistMetadata Metadata { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
|
||||
}
|
82
Wabbajack.App.Blazor/Components/ModlistItem.razor.scss
Normal file
@ -0,0 +1,82 @@
|
||||
@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;
|
||||
}
|
||||
}
|
12
Wabbajack.App.Blazor/Components/News.razor
Normal file
@ -0,0 +1,12 @@
|
||||
@namespace Wabbajack.App.Blazor.Components
|
||||
|
||||
<div class="container">
|
||||
<div class="info">
|
||||
<p class="title">
|
||||
Wabbajack 3.0
|
||||
</p>
|
||||
<p class="description">
|
||||
[TBI] News ticker thing.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
31
Wabbajack.App.Blazor/Components/News.razor.scss
Normal file
@ -0,0 +1,31 @@
|
||||
.container {
|
||||
background-image: linear-gradient(30deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.8) 30%, rgba(255, 255, 255, 0) 100%), url(images/Banner_Dark_Transparent.png);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1rem 1.5rem;
|
||||
max-width: 56rem;
|
||||
height: 9rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(5px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
|
||||
.info {
|
||||
align-self: flex-end;
|
||||
|
||||
.title {
|
||||
color: white;
|
||||
font-weight: 100;
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: grey;
|
||||
}
|
||||
}
|
||||
}
|
27
Wabbajack.App.Blazor/Components/OptionCheckbox.razor
Normal file
@ -0,0 +1,27 @@
|
||||
@namespace Wabbajack.App.Blazor.Components
|
||||
|
||||
<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);
|
||||
}
|
||||
|
||||
}
|
57
Wabbajack.App.Blazor/Components/OptionCheckbox.razor.scss
Normal file
@ -0,0 +1,57 @@
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
18
Wabbajack.App.Blazor/Components/ProgressBar.razor
Normal file
@ -0,0 +1,18 @@
|
||||
@using Wabbajack.RateLimiter
|
||||
|
||||
@namespace Wabbajack.App.Blazor.Components
|
||||
|
||||
<div id="progress-bar">
|
||||
<progress value="@Percentage.Value"></progress>
|
||||
<span class="text">@Text</span>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public Percent Percentage { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Text { get; set; }
|
||||
|
||||
}
|
30
Wabbajack.App.Blazor/Components/ProgressBar.razor.scss
Normal file
@ -0,0 +1,30 @@
|
||||
#progress-bar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
progress {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
appearance: none;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
progress[value]::-webkit-progress-bar {
|
||||
background-color: #ededed;
|
||||
border-radius: 40px;
|
||||
}
|
||||
|
||||
progress[value]::-webkit-progress-value {
|
||||
border-radius: 40px;
|
||||
background-color: mediumpurple;
|
||||
}
|
||||
|
||||
.text {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
color: blue;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
}
|
11
Wabbajack.App.Blazor/Components/SideBar.razor
Normal file
@ -0,0 +1,11 @@
|
||||
@namespace Wabbajack.App.Blazor.Components
|
||||
|
||||
<div id="side-bar">
|
||||
@* TODO: [Low] Replace logo with SVG? *@
|
||||
<img class="logo" src="images/Logo_Dark_Transparent.png" alt="Wabbajack Logo">
|
||||
<div class="socials">
|
||||
<img src="images/icons/patreon.svg" alt="">
|
||||
<img src="images/icons/github.svg" alt="">
|
||||
<img src="images/icons/discord.svg" alt="">
|
||||
</div>
|
||||
</div>
|
33
Wabbajack.App.Blazor/Components/SideBar.razor.scss
Normal file
@ -0,0 +1,33 @@
|
||||
@import "../Shared/Globals.scss";
|
||||
|
||||
#side-bar {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: $sidebar-width;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-right: 1px solid #404040;
|
||||
backdrop-filter: brightness(0.8);
|
||||
|
||||
.logo {
|
||||
padding: 0.5rem;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
vertical-align: middle;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.socials {
|
||||
width: 30px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
||||
}
|
58
Wabbajack.App.Blazor/Components/TopBar.razor
Normal file
@ -0,0 +1,58 @@
|
||||
@using Wabbajack.App.Blazor.Pages
|
||||
@using Wabbajack.App.Blazor.Shared
|
||||
@using Wabbajack.App.Blazor.State
|
||||
|
||||
@namespace Wabbajack.App.Blazor.Components
|
||||
|
||||
@* TODO: [Low] Clean this up a bit. *@
|
||||
<header id="top-bar">
|
||||
<nav class="@(GlobalState.NavigationAllowed ? "" : "disallow")">
|
||||
<ul>
|
||||
<li>
|
||||
<div class='item @CurrentPage("")' @onclick='() => Navigate("")'>Play</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class='item @CurrentPage("Gallery")' @onclick='() => Navigate("Gallery")'>Gallery</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class='item @CurrentPage("Install")' @onclick='() => Navigate("Install")'>Install</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class='item @CurrentPage("Create")' @onclick='() => Navigate("Create")'>Create</div>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="settings">
|
||||
<InteractionIcon Icon="images/icons/adjust.svg" Label="Settings" Size="100%" OnClick="@(() => Navigate("Settings"))"/>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@code {
|
||||
|
||||
[Inject]
|
||||
NavigationManager _navigationManager { get; set; }
|
||||
|
||||
[Inject]
|
||||
GlobalState GlobalState { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
protected MainLayout _mainLayout { get; set; }
|
||||
|
||||
private void Navigate(string page)
|
||||
{
|
||||
_navigationManager.NavigateTo(page);
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_navigationManager.LocationChanged += (o, args) => StateHasChanged();
|
||||
GlobalState.OnNavigationStateChange += StateHasChanged;
|
||||
}
|
||||
|
||||
private string CurrentPage(string page)
|
||||
{
|
||||
string relativePath = _navigationManager.ToBaseRelativePath(_navigationManager.Uri).ToLower();
|
||||
return page.ToLower() == relativePath ? "active" : "";
|
||||
}
|
||||
|
||||
}
|
66
Wabbajack.App.Blazor/Components/TopBar.razor.scss
Normal file
@ -0,0 +1,66 @@
|
||||
@import "../Shared/Globals.scss";
|
||||
|
||||
#top-bar {
|
||||
position: fixed;
|
||||
width: calc(100% - #{$sidebar-width});
|
||||
height: $header-height;
|
||||
background-color: transparent;
|
||||
backdrop-filter: blur(5px) grayscale(10%);
|
||||
z-index: 2;
|
||||
|
||||
font-family: $raleway-font;
|
||||
text-transform: uppercase;
|
||||
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
padding: 10px;
|
||||
|
||||
nav {
|
||||
font-weight: 100;
|
||||
font-size: 1em;
|
||||
line-height: 2rem;
|
||||
|
||||
&.disallow {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
ul {
|
||||
li {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
|
||||
.item {
|
||||
color: #dddddd;
|
||||
display: block;
|
||||
padding: 0.5rem 1rem;
|
||||
text-decoration: none;
|
||||
transition: border 100ms ease-in-out, color 100ms ease-in-out;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
|
||||
&.active {
|
||||
color: white;
|
||||
border-bottom: 2px solid #824dc3;
|
||||
}
|
||||
|
||||
&:not(.active):hover {
|
||||
border-bottom: 2px solid $accent-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings {
|
||||
height: 60%;
|
||||
margin: auto 1rem;
|
||||
cursor: pointer;
|
||||
filter: brightness(80%);
|
||||
transition: filter 100ms ease-in-out;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(100%);
|
||||
}
|
||||
}
|
||||
}
|
28
Wabbajack.App.Blazor/Components/VirtualLogger.razor
Normal file
@ -0,0 +1,28 @@
|
||||
@using Wabbajack.App.Blazor.Models
|
||||
|
||||
@namespace Wabbajack.App.Blazor.Components
|
||||
|
||||
<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();
|
||||
}
|
||||
|
||||
}
|
23
Wabbajack.App.Blazor/Components/VirtualLogger.razor.scss
Normal 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;
|
||||
}
|
||||
}
|
13
Wabbajack.App.Blazor/Main.razor
Normal file
@ -0,0 +1,13 @@
|
||||
<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>
|
18
Wabbajack.App.Blazor/MainWindow.xaml
Normal file
@ -0,0 +1,18 @@
|
||||
<Window x:Class="Wabbajack.App.Blazor.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Wabbajack.App.Blazor"
|
||||
xmlns:blazor="clr-namespace:Microsoft.AspNetCore.Components.WebView.Wpf;assembly=Microsoft.AspNetCore.Components.WebView.Wpf"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" Height="750" Width="1200" MinHeight="750" MinWidth="1200">
|
||||
<Grid Background="#121212">
|
||||
<blazor:BlazorWebView HostPage="wwwroot\index.html" Services="{StaticResource services}"
|
||||
x:Name="blazorWebView1">
|
||||
<blazor:BlazorWebView.RootComponents>
|
||||
<blazor:RootComponent Selector="#app" ComponentType="{x:Type local:Main}" />
|
||||
</blazor:BlazorWebView.RootComponents>
|
||||
</blazor:BlazorWebView>
|
||||
</Grid>
|
||||
</Window>
|
57
Wabbajack.App.Blazor/MainWindow.xaml.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Wabbajack.App.Blazor.Models;
|
||||
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<MainWindow> _logger;
|
||||
private readonly LoggerProvider _loggerProvider;
|
||||
private readonly SystemParametersConstructor _systemParams;
|
||||
|
||||
public MainWindow(ILogger<MainWindow> logger, IServiceProvider serviceProvider, LoggerProvider loggerProvider,
|
||||
SystemParametersConstructor systemParams)
|
||||
{
|
||||
_logger = logger;
|
||||
_loggerProvider = loggerProvider;
|
||||
_systemParams = systemParams;
|
||||
Resources.Add("services", serviceProvider);
|
||||
InitializeComponent();
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: [Low] Not sure how to set this up.
|
||||
//_logger.LogInformation("Wabbajack Build - {Sha}", ThisAssembly.Git.Sha);
|
||||
_logger.LogInformation("Running in {EntryPoint}", KnownFolders.EntryPoint);
|
||||
|
||||
SystemParameters p = _systemParams.Create();
|
||||
|
||||
_logger.LogInformation("Detected Windows Version: {Version}", Environment.OSVersion.VersionString);
|
||||
|
||||
_logger.LogInformation(
|
||||
"System settings - ({MemorySize} RAM) ({PageSize} Page), Display: {ScreenWidth} x {ScreenHeight} ({Vram} VRAM - VideoMemorySizeMb={ENBVRam})",
|
||||
p.SystemMemorySize.ToFileSizeString(), p.SystemPageSize.ToFileSizeString(), p.ScreenWidth, p.ScreenHeight,
|
||||
p.VideoMemorySize.ToFileSizeString(), p.EnbLEVRAMSize);
|
||||
|
||||
if (p.SystemPageSize == 0)
|
||||
_logger.LogInformation(
|
||||
"Page file is disabled! Consider increasing to 20000MB. A disabled page file can cause crashes and poor in-game performance");
|
||||
else if (p.SystemPageSize < 2e+10)
|
||||
_logger.LogInformation(
|
||||
"Page file below recommended! Consider increasing to 20000MB. A suboptimal page file can cause crashes and poor in-game performance");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error during Main Window startup.");
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Required so compiler doesn't complain about not finding the type. [MC3050]
|
||||
public partial class Main { }
|
3
Wabbajack.App.Blazor/Models/Install.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Wabbajack.App.Blazor.Models;
|
||||
|
||||
public class Install { }
|
149
Wabbajack.App.Blazor/Models/LoggerProvider.cs
Normal file
@ -0,0 +1,149 @@
|
||||
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<ILogMessage> _messagesFiltered;
|
||||
private readonly DateTime _startupTime;
|
||||
|
||||
private long _messageId;
|
||||
private readonly SourceCache<ILogMessage, long> _messageLog = new(m => m.MessageId);
|
||||
private readonly Subject<ILogMessage> _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<ILogMessage> Messages => _messages;
|
||||
public AbsolutePath LogPath { get; }
|
||||
public ReadOnlyObservableCollection<ILogMessage> MessageLog => _messagesFiltered;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disposables.Dispose();
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new Logger(this, categoryName);
|
||||
}
|
||||
|
||||
private void LogToFile(ILogMessage logMessage)
|
||||
{
|
||||
string? 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<object> Scopes = ImmutableList<object>.Empty;
|
||||
|
||||
public Logger(LoggerProvider provider, string categoryName)
|
||||
{
|
||||
_categoryName = categoryName;
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
Debug.WriteLine($"{logLevel} - {formatter(state, exception)}");
|
||||
_provider._messages.OnNext(new LogMessage<TState>(DateTime.UtcNow, _provider.NextMessageId(), logLevel,
|
||||
eventId, state, exception, formatter));
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(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<TState>(DateTime TimeStamp, long MessageId, LogLevel LogLevel, EventId EventId,
|
||||
TState State, Exception? Exception, Func<TState, Exception?, string> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
Wabbajack.App.Blazor/Pages/Configure.razor
Normal file
@ -0,0 +1,77 @@
|
||||
@page "/Configure"
|
||||
|
||||
@using Wabbajack.App.Blazor.State
|
||||
|
||||
@namespace Wabbajack.App.Blazor.Pages
|
||||
|
||||
<div id="content">
|
||||
<div class="install-background">
|
||||
<img src="@Image" alt="">
|
||||
</div>
|
||||
<div class="list">
|
||||
@* TODO: [High] Find a cleaner way to show/hide components based on state. *@
|
||||
@* TODO: [Low] Split each "side" into their own components? *@
|
||||
<div class="left-side">
|
||||
@if (!string.IsNullOrEmpty(ModList.Name))
|
||||
{
|
||||
if (InstallState != GlobalState.InstallStateEnum.Installing)
|
||||
{
|
||||
<InfoBlock Title="@ModList.Name" Subtitle="@ModList.Author" Comment="@ModList.Version.ToString()" Description="@ModList.Description"/>
|
||||
}
|
||||
else if (InstallState == GlobalState.InstallStateEnum.Installing)
|
||||
{
|
||||
<InfoBlock Supertitle="Installing..." Title="@ModList.Name" Subtitle="@StatusText"/>
|
||||
// TODO: [Low] Step logging.
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="right-side">
|
||||
@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 == GlobalState.InstallStateEnum.Installing)
|
||||
{
|
||||
<div class="logger-container">
|
||||
<VirtualLogger Messages="_loggerProvider.Messages"/>
|
||||
</div>
|
||||
}
|
||||
@if (InstallState != GlobalState.InstallStateEnum.Installing)
|
||||
{
|
||||
<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</span>
|
||||
<span class="install-location" @onclick="SelectInstallFolder">@InstallPath</span>
|
||||
<span class="download-location" @onclick="SelectDownloadFolder">@DownloadPath</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>
|
158
Wabbajack.App.Blazor/Pages/Configure.razor.cs
Normal file
@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.DTOs.JsonConverters;
|
||||
using Wabbajack.Installer;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.App.Blazor.Utility;
|
||||
using Wabbajack.Downloaders.GameFile;
|
||||
using Wabbajack.Hashing.xxHash64;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.App.Blazor.Models;
|
||||
using Wabbajack.App.Blazor.State;
|
||||
|
||||
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; }
|
||||
|
||||
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 DownloadPath { get; set; }
|
||||
|
||||
private string StatusText { get; set; }
|
||||
public GlobalState.InstallStateEnum InstallState { get; set; }
|
||||
private LoggerProvider.ILogMessage CurrentLog { get; set; }
|
||||
|
||||
private const string InstallSettingsPrefix = "install-settings-";
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private async Task CheckValidInstallPath()
|
||||
{
|
||||
if (GlobalState.ModListPath == AbsolutePath.Empty) return;
|
||||
|
||||
ModListPath = GlobalState.ModListPath;
|
||||
ModList = await StandardInstaller.LoadFromFile(_dtos, ModListPath);
|
||||
GlobalState.ModList = ModList;
|
||||
|
||||
string hex = (await ModListPath.ToString().Hash()).ToHex();
|
||||
var prevSettings = await _settingsManager.Load<SavedInstallSettings>(InstallSettingsPrefix + hex);
|
||||
|
||||
if (prevSettings.ModListLocation == ModListPath)
|
||||
{
|
||||
ModListPath = prevSettings.ModListLocation;
|
||||
InstallPath = prevSettings.InstallLocation;
|
||||
DownloadPath = prevSettings.DownloadLoadction;
|
||||
//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())}";
|
||||
}
|
||||
|
||||
private async void SelectInstallFolder()
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsolutePath? thing = await Dialog.ShowDialogNonBlocking(true);
|
||||
if (thing != null) InstallPath = (AbsolutePath)thing;
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Print(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async void SelectDownloadFolder()
|
||||
{
|
||||
try
|
||||
{
|
||||
AbsolutePath? thing = await Dialog.ShowDialogNonBlocking(true);
|
||||
if (thing != null) DownloadPath = (AbsolutePath)thing;
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Print(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Install()
|
||||
{
|
||||
GlobalState.InstallState = GlobalState.InstallStateEnum.Installing;
|
||||
await Task.Run(BeginInstall);
|
||||
}
|
||||
|
||||
private async Task BeginInstall()
|
||||
{
|
||||
string postfix = (await ModListPath.ToString().Hash()).ToHex();
|
||||
await _settingsManager.Save(InstallSettingsPrefix + postfix, new SavedInstallSettings
|
||||
{
|
||||
ModListLocation = ModListPath,
|
||||
InstallLocation = InstallPath,
|
||||
DownloadLoadction = DownloadPath
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
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)
|
||||
});
|
||||
|
||||
|
||||
installer.OnStatusUpdate = update =>
|
||||
{
|
||||
if (StatusText != update.StatusText)
|
||||
{
|
||||
StatusText = update.StatusText;
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
};
|
||||
|
||||
await installer.Begin(CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Print(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class SavedInstallSettings
|
||||
{
|
||||
public AbsolutePath ModListLocation { get; set; }
|
||||
public AbsolutePath InstallLocation { get; set; }
|
||||
public AbsolutePath DownloadLoadction { get; set; }
|
||||
public ModlistMetadata Metadata { get; set; }
|
||||
}
|
115
Wabbajack.App.Blazor/Pages/Configure.razor.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
Wabbajack.App.Blazor/Pages/Create.razor
Normal file
@ -0,0 +1,8 @@
|
||||
@page "/Create"
|
||||
@namespace Wabbajack.App.Blazor.Pages
|
||||
|
||||
<div id="content">
|
||||
<div class="resources">
|
||||
|
||||
</div>
|
||||
</div>
|
24
Wabbajack.App.Blazor/Pages/Gallery.razor
Normal file
@ -0,0 +1,24 @@
|
||||
@page "/Gallery"
|
||||
|
||||
@using Wabbajack.DTOs
|
||||
@using Wabbajack.RateLimiter
|
||||
|
||||
@namespace Wabbajack.App.Blazor.Pages
|
||||
|
||||
<div id="content">
|
||||
@foreach (ModlistMetadata modlist in _listItems)
|
||||
{
|
||||
<ModlistItem Metadata=@modlist>
|
||||
<InteractionIcon Icon="images/icons/install.svg" Label="Install" Size="75px" OnClick="@(() => OnClickDownload(modlist))"/>
|
||||
<InteractionIcon Icon="images/icons/info.svg" Label="Information" Size="75px" OnClick="@(() => OnClickInformation(modlist))"/>
|
||||
</ModlistItem>
|
||||
}
|
||||
@if (DownloadProgress != Percent.Zero)
|
||||
{
|
||||
<BottomBar Image="@DownloadingMetaData.Links.ImageUri" Title="Downloading..." Subtitle="@DownloadingMetaData.Title">
|
||||
<div style="height:1.5rem;">
|
||||
<ProgressBar Percentage="@DownloadProgress" Text="@DownloadProgress.Value.ToString()"/>
|
||||
</div>
|
||||
</BottomBar>
|
||||
}
|
||||
</div>
|
89
Wabbajack.App.Blazor/Pages/Gallery.razor.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
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;
|
||||
|
||||
namespace Wabbajack.App.Blazor.Pages;
|
||||
|
||||
public partial class Gallery
|
||||
{
|
||||
[Inject] private GlobalState GlobalState { get; set; }
|
||||
[Inject] private NavigationManager NavigationManager { get; set; }
|
||||
[Inject] private ILogger<Gallery> _logger { get; set; }
|
||||
[Inject] private Client _client { get; set; }
|
||||
[Inject] private ModListDownloadMaintainer _maintainer { get; set; }
|
||||
|
||||
public Percent DownloadProgress { get; set; } = Percent.Zero;
|
||||
public ModlistMetadata DownloadingMetaData { get; set; } = new ModlistMetadata();
|
||||
|
||||
private List<ModlistMetadata> _listItems { get; set; } = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Getting modlists...");
|
||||
ModlistMetadata[] modLists = await _client.LoadLists();
|
||||
_listItems.AddRange(modLists.ToList());
|
||||
StateHasChanged();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
private async void OnClickDownload(ModlistMetadata metadata)
|
||||
{
|
||||
// GlobalState.NavigationAllowed = !GlobalState.NavigationAllowed;
|
||||
await Download(metadata);
|
||||
}
|
||||
|
||||
private async void OnClickInformation(ModlistMetadata metadata)
|
||||
{
|
||||
// TODO: [High] Implement information modal.
|
||||
}
|
||||
|
||||
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));
|
||||
try
|
||||
{
|
||||
(IObservable<Percent> progress, Task task) = _maintainer.DownloadModlist(metadata);
|
||||
IDisposable dispose = progress.Subscribe(p => DownloadProgress = p);
|
||||
|
||||
await task;
|
||||
//await _wjClient.SendMetric("downloading", Metadata.Title);
|
||||
dispose.Dispose();
|
||||
|
||||
AbsolutePath path = KnownFolders.EntryPoint.Combine("downloaded_mod_lists", metadata.Links.MachineURL).WithExtension(Ext.Wabbajack);
|
||||
GlobalState.ModListPath = path;
|
||||
NavigationManager.NavigateTo("/Configure");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Print(e.Message);
|
||||
}
|
||||
|
||||
await timer.DisposeAsync();
|
||||
GlobalState.NavigationAllowed = true;
|
||||
}
|
||||
}
|
6
Wabbajack.App.Blazor/Pages/Gallery.razor.scss
Normal file
@ -0,0 +1,6 @@
|
||||
#content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-evenly;
|
||||
}
|
26
Wabbajack.App.Blazor/Pages/Install.razor
Normal file
@ -0,0 +1,26 @@
|
||||
@page "/Install"
|
||||
|
||||
@namespace Wabbajack.App.Blazor.Pages
|
||||
|
||||
<div id="content">
|
||||
<div class="select">
|
||||
<div class="browse">
|
||||
<img src="images/icons/cloud-download.svg" alt="Browse Gallery">
|
||||
<span>Browse the Gallery</span>
|
||||
</div>
|
||||
<div class="disk">
|
||||
<img src="images/icons/disk.svg" @onclick="SelectFile" alt="Install from File">
|
||||
<span>Install from File</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="recent">
|
||||
<div class="title">[TBI] Recently downloaded</div>
|
||||
<div class="modlist">
|
||||
<img src="https://i.imgur.com/9jnSPcX.png" alt="">
|
||||
<div class="info">
|
||||
<div class="title">[Title]</div>
|
||||
<div class="description">[Description]</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
28
Wabbajack.App.Blazor/Pages/Install.razor.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||
using Wabbajack.App.Blazor.State;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.App.Blazor.Pages;
|
||||
|
||||
public partial class Install
|
||||
{
|
||||
[Inject] private NavigationManager NavigationManager { get; set; }
|
||||
[Inject] private GlobalState GlobalState { get; set; }
|
||||
|
||||
private void SelectFile()
|
||||
{
|
||||
using (var dialog = new CommonOpenFileDialog())
|
||||
{
|
||||
dialog.Multiselect = false;
|
||||
dialog.Filters.Add(new CommonFileDialogFilter("Wabbajack File", "*" + Ext.Wabbajack));
|
||||
if (dialog.ShowDialog() != CommonFileDialogResult.Ok) return;
|
||||
GlobalState.ModListPath = dialog.FileName.ToAbsolutePath();
|
||||
}
|
||||
|
||||
NavigationManager.NavigateTo("/Configure");
|
||||
}
|
||||
|
||||
private void VerifyFile(AbsolutePath path) { }
|
||||
}
|
49
Wabbajack.App.Blazor/Pages/Install.razor.scss
Normal file
@ -0,0 +1,49 @@
|
||||
$install-icon-size: 15rem;
|
||||
|
||||
#content {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
|
||||
.select {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
border: solid 1px red;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: $install-icon-size;
|
||||
height: $install-icon-size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.recent {
|
||||
.title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.modlist {
|
||||
img {
|
||||
height: 4rem;
|
||||
width: auto;
|
||||
border: solid 1px red;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
Wabbajack.App.Blazor/Pages/Play.razor
Normal file
@ -0,0 +1,23 @@
|
||||
@page "/"
|
||||
|
||||
@namespace Wabbajack.App.Blazor.Pages
|
||||
|
||||
<News/>
|
||||
<div id="content"></div>
|
||||
|
||||
@code {
|
||||
// List<string> InstalledLists = new();
|
||||
// protected override async Task OnInitializedAsync()
|
||||
// {
|
||||
// AbsolutePath installedModlists = KnownFolders.WabbajackAppLocal.Combine("installed_modlists.json");
|
||||
// string toJson = await installedModlists.ReadAllTextAsync();
|
||||
// JObject installedJson = JObject.Parse(toJson);
|
||||
// foreach ((string? key, JToken? value) in installedJson)
|
||||
// {
|
||||
// foreach (JObject obj in value)
|
||||
// {
|
||||
// Console.WriteLine(obj.Properties());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
32
Wabbajack.App.Blazor/Pages/Play.razor.scss
Normal file
@ -0,0 +1,32 @@
|
||||
#content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
//height: inherit;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
//
|
||||
//.header {
|
||||
// flex: 2;
|
||||
// display: flex;
|
||||
// background-image: url("images/Logo_Dark_Transparent.png");
|
||||
// background-position: bottom;
|
||||
// background-repeat: no-repeat;
|
||||
// background-size: contain;
|
||||
//
|
||||
// .text {
|
||||
// background-image: url("images/Letters_Dark_Transparent.png");
|
||||
// background-repeat: no-repeat;
|
||||
// margin: 0 auto;
|
||||
// background-size: contain;
|
||||
// height: 50%;
|
||||
// width: 100px;
|
||||
// max-width: 1000px;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//.menu {
|
||||
// flex: 1;
|
||||
// color: green;
|
||||
// height: 200px;
|
||||
//}
|
8
Wabbajack.App.Blazor/Pages/Settings.razor
Normal file
@ -0,0 +1,8 @@
|
||||
@page "/Settings"
|
||||
@namespace Wabbajack.App.Blazor.Pages
|
||||
|
||||
<div id="content">
|
||||
<div class="resources">
|
||||
|
||||
</div>
|
||||
</div>
|
23
Wabbajack.App.Blazor/Pages/Settings.razor.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Wabbajack.App.Models;
|
||||
|
||||
namespace Wabbajack.App.Blazor.Pages;
|
||||
|
||||
public partial class Settings
|
||||
{
|
||||
[Inject] private ResourceSettingsManager _resourceSettingsManager { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
ResourceSettingsManager.ResourceSetting resource = await _resourceSettingsManager.GetSettings("Downloads");
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex) { }
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
}
|
21
Wabbajack.App.Blazor/Shared/Globals.scss
Normal file
@ -0,0 +1,21 @@
|
||||
@font-face {
|
||||
font-family: "Raleway";
|
||||
src: url("fonts/Raleway-Variable.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "YanoneKaffeesatz";
|
||||
src: url("fonts/YanoneKaffeesatz-Variable.ttf");
|
||||
}
|
||||
|
||||
$primary-background-color: #121212;
|
||||
$secondary-background-color: #0A0A0A;
|
||||
$accent-color: #5E437F;
|
||||
|
||||
$header-height: 65px;
|
||||
$sidebar-width: 75px;
|
||||
|
||||
$fallback-font: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
|
||||
$raleway-font: 'Raleway', $fallback-font;
|
||||
$yanone-font: 'YanoneKaffeesatz', $fallback-font;
|
12
Wabbajack.App.Blazor/Shared/MainLayout.razor
Normal file
@ -0,0 +1,12 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
@namespace Wabbajack.App.Blazor.Shared
|
||||
|
||||
<div id="background"></div>
|
||||
<SideBar/>
|
||||
<div id="wrapper">
|
||||
<TopBar/>
|
||||
<div id="page">
|
||||
@Body
|
||||
</div>
|
||||
</div>
|
20
Wabbajack.App.Blazor/Shared/MainLayout.razor.scss
Normal file
@ -0,0 +1,20 @@
|
||||
@import "./Globals.scss";
|
||||
|
||||
#background {
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
background: linear-gradient(320deg, #151022, #1f0d2a, #100214);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
width: calc(100% - #{$sidebar-width});
|
||||
float: right;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#page {
|
||||
margin-top: $header-height;
|
||||
height: calc(100% - #{$header-height});
|
||||
}
|
77
Wabbajack.App.Blazor/State/GlobalState.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System;
|
||||
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 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
|
||||
}
|
29
Wabbajack.App.Blazor/Utility/Dialog.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||
using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.App.Blazor.Utility;
|
||||
|
||||
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)
|
||||
{
|
||||
return await Task.Factory.StartNew(() =>
|
||||
{
|
||||
Window newWindow = new();
|
||||
var dialog = new CommonOpenFileDialog();
|
||||
dialog.IsFolderPicker = isFolderPicker;
|
||||
dialog.Multiselect = false;
|
||||
CommonFileDialogResult result = dialog.ShowDialog(newWindow);
|
||||
return result == CommonFileDialogResult.Ok ? dialog.FileName : null;
|
||||
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext())
|
||||
.ContinueWith(result => result.Result?.ToAbsolutePath())
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
153
Wabbajack.App.Blazor/Utility/SystemParametersConstructor.cs
Normal file
@ -0,0 +1,153 @@
|
||||
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 static PInvoke.User32;
|
||||
using UnmanagedType = System.Runtime.InteropServices.UnmanagedType;
|
||||
|
||||
namespace Wabbajack.App.Blazor.Utility;
|
||||
|
||||
// Much of the GDI code here is taken from : https://github.com/ModOrganizer2/modorganizer/blob/master/src/envmetrics.cpp
|
||||
// Thanks to MO2 for being good citizens and supporting OSS code
|
||||
public class SystemParametersConstructor
|
||||
{
|
||||
private readonly ILogger<SystemParametersConstructor> _logger;
|
||||
|
||||
public SystemParametersConstructor(ILogger<SystemParametersConstructor> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private IEnumerable<(int Width, int Height, bool IsPrimary)> GetDisplays()
|
||||
{
|
||||
// Needed to make sure we get the right values from this call
|
||||
SetProcessDPIAware();
|
||||
unsafe
|
||||
{
|
||||
List<(int Width, int Height, bool IsPrimary)>? 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);
|
||||
if (success)
|
||||
col.Add((mi.Monitor.right - mi.Monitor.left, mi.Monitor.bottom - mi.Monitor.top,
|
||||
mi.Flags == MONITORINFO_Flags.MONITORINFOF_PRIMARY));
|
||||
|
||||
return true;
|
||||
}, IntPtr.Zero);
|
||||
return col;
|
||||
}
|
||||
}
|
||||
|
||||
public SystemParameters Create()
|
||||
{
|
||||
(int width, int 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;
|
||||
|
||||
unsafe
|
||||
{
|
||||
using DXGI? api = DXGI.GetApi();
|
||||
|
||||
IDXGIFactory1* factory1 = default;
|
||||
|
||||
try
|
||||
{
|
||||
//https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-createdxgifactory1
|
||||
SilkMarshal.ThrowHResult(api.CreateDXGIFactory1(SilkMarshal.GuidPtrOf<IDXGIFactory1>(), (void**)&factory1));
|
||||
|
||||
uint 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);
|
||||
|
||||
Exception? exception = Marshal.GetExceptionForHR(res);
|
||||
if (exception != null) break;
|
||||
|
||||
AdapterDesc1 adapterDesc = default;
|
||||
|
||||
//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;
|
||||
|
||||
ulong maxMemory = Math.Max(systemMemory, videoMemory);
|
||||
if (maxMemory > dxgiMemory)
|
||||
dxgiMemory = maxMemory;
|
||||
|
||||
adapter1->Release();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "While getting SystemParameters");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (factory1->LpVtbl != (void**)IntPtr.Zero)
|
||||
factory1->Release();
|
||||
}
|
||||
}
|
||||
|
||||
MEMORYSTATUSEX? memory = GetMemoryStatus();
|
||||
return new SystemParameters
|
||||
{
|
||||
ScreenWidth = width,
|
||||
ScreenHeight = height,
|
||||
VideoMemorySize = (long)dxgiMemory,
|
||||
SystemMemorySize = (long)memory.ullTotalPhys,
|
||||
SystemPageSize = (long)memory.ullTotalPageFile - (long)memory.ullTotalPhys
|
||||
};
|
||||
}
|
||||
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern bool GlobalMemoryStatusEx([In] [Out] MEMORYSTATUSEX lpBuffer);
|
||||
|
||||
public static MEMORYSTATUSEX GetMemoryStatus()
|
||||
{
|
||||
var mstat = new MEMORYSTATUSEX();
|
||||
GlobalMemoryStatusEx(mstat);
|
||||
return mstat;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public class MEMORYSTATUSEX
|
||||
{
|
||||
public uint dwLength;
|
||||
public uint dwMemoryLoad;
|
||||
public ulong ullTotalPhys;
|
||||
public ulong ullAvailPhys;
|
||||
public ulong ullTotalPageFile;
|
||||
public ulong ullAvailPageFile;
|
||||
public ulong ullTotalVirtual;
|
||||
public ulong ullAvailVirtual;
|
||||
public ulong ullAvailExtendedVirtual;
|
||||
|
||||
public MEMORYSTATUSEX()
|
||||
{
|
||||
dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
|
||||
}
|
||||
}
|
||||
}
|
50
Wabbajack.App.Blazor/Wabbajack.App.Blazor.csproj
Normal file
@ -0,0 +1,50 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<PublishSingleFile>True</PublishSingleFile>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<SupportedPlatform Include="browser" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DynamicData" Version="7.4.9" />
|
||||
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Wpf" Version="6.0.101-preview.11.2349" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.1" />
|
||||
<PackageReference Include="PInvoke.User32" Version="0.7.104" />
|
||||
<PackageReference Include="Silk.NET.DXGI" Version="2.12.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="wwwroot\index.html">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wabbajack.Common\Wabbajack.Common.csproj" />
|
||||
<ProjectReference Include="..\Wabbajack.Compiler\Wabbajack.Compiler.csproj" />
|
||||
<ProjectReference Include="..\Wabbajack.Installer\Wabbajack.Installer.csproj" />
|
||||
<ProjectReference Include="..\Wabbajack.Paths.IO\Wabbajack.Paths.IO.csproj" />
|
||||
<ProjectReference Include="..\Wabbajack.Services.OSIntegrated\Wabbajack.Services.OSIntegrated.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- dotnet tool install Excubo.WebCompiler -g -->
|
||||
<Target Name="TestWebCompiler" BeforeTargets="PreBuildEvent">
|
||||
<Exec Command="webcompiler -h" ContinueOnError="true" StandardOutputImportance="low" StandardErrorImportance="low" LogStandardErrorAsError="false" IgnoreExitCode="true">
|
||||
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
|
||||
</Exec>
|
||||
</Target>
|
||||
|
||||
<Target Name="CompileStaticAssets" AfterTargets="TestWebCompiler" Condition="'$(ErrorCode)' == '0'">
|
||||
<Exec Command="webcompiler -r .\ -c webcompilerconfiguration.json" StandardOutputImportance="high" StandardErrorImportance="high" />
|
||||
</Target>
|
||||
</Project>
|
8
Wabbajack.App.Blazor/_Imports.razor
Normal file
@ -0,0 +1,8 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using Wabbajack.App.Blazor.Components
|
23
Wabbajack.App.Blazor/webcompilerconfiguration.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"Minifiers": {
|
||||
"GZip": false,
|
||||
"Enabled": false
|
||||
},
|
||||
"Autoprefix": {
|
||||
"Enabled": false
|
||||
},
|
||||
"CompilerSettings": {
|
||||
"Sass": {
|
||||
"IndentType": "Space",
|
||||
"IndentWidth": 2,
|
||||
"OutputStyle": "Nested",
|
||||
"Precision": 5,
|
||||
"RelativeUrls": true,
|
||||
"LineFeed": "Lf",
|
||||
"SourceMap": false
|
||||
}
|
||||
},
|
||||
"Output": {
|
||||
"Preserve": true
|
||||
}
|
||||
}
|
BIN
Wabbajack.App.Blazor/wwwroot/fonts/Raleway-Variable.ttf
Normal file
BIN
Wabbajack.App.Blazor/wwwroot/fonts/YanoneKaffeesatz-Variable.ttf
Normal file
BIN
Wabbajack.App.Blazor/wwwroot/images/Banner.png
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/Banner_Dark.png
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/Banner_Dark_Transparent.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/Banner_Transparent.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/Letters.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/Letters_Dark.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/Letters_Dark_Transparent.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/Letters_Transparent.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/Logo.png
Normal file
After Width: | Height: | Size: 152 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/Logo_Dark_Transparent.png
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/Logo_Transparent.png
Normal file
After Width: | Height: | Size: 120 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/MO2Button.png
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/VortexButton.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/WabbaFilled.png
Normal file
After Width: | Height: | Size: 187 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/WabbaInverted.png
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/Wabba_Ded.png
Normal file
After Width: | Height: | Size: 182 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/Wabba_Mouth.png
Normal file
After Width: | Height: | Size: 272 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/Wabba_Mouth_No_Text.png
Normal file
After Width: | Height: | Size: 174 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/Wabba_Mouth_Small.png
Normal file
After Width: | Height: | Size: 58 KiB |
9
Wabbajack.App.Blazor/wwwroot/images/icons/adjust.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
|
||||
width="1024px" height="1024px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M30 8h-4.1c-.5-2.3-2.5-4-4.9-4s-4.4 1.7-4.9 4H2v2h14.1c.5 2.3 2.5 4 4.9 4s4.4-1.7 4.9-4H30V8zm-9 4c-1.7 0-3-1.3-3-3s1.3-3 3-3s3 1.3 3 3s-1.3 3-3 3z"
|
||||
fill="#FFF"/>
|
||||
<path
|
||||
d="M2 24h4.1c.5 2.3 2.5 4 4.9 4s4.4-1.7 4.9-4H30v-2H15.9c-.5-2.3-2.5-4-4.9-4s-4.4 1.7-4.9 4H2v2zm9-4c1.7 0 3 1.3 3 3s-1.3 3-3 3s-3-1.3-3-3s1.3-3 3-3z"
|
||||
fill="#FFF"/>
|
||||
</svg>
|
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
|
||||
width="1024" height="1024" preserveAspectRatio="xMidYMid meet" viewBox="0 0 1024 1024">
|
||||
<path d="M624 706.3h-74.1V464c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v242.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.7a8 8 0 0 0 12.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9z"
|
||||
fill="#FFF"/>
|
||||
<path d="M811.4 366.7C765.6 245.9 648.9 160 512.2 160S258.8 245.8 213 366.6C127.3 389.1 64 467.2 64 560c0 110.5 89.5 200 199.9 200H304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8h-40.1c-33.7 0-65.4-13.4-89-37.7c-23.5-24.2-36-56.8-34.9-90.6c.9-26.4 9.9-51.2 26.2-72.1c16.7-21.3 40.1-36.8 66.1-43.7l37.9-9.9l13.9-36.6c8.6-22.8 20.6-44.1 35.7-63.4a245.6 245.6 0 0 1 52.4-49.9c41.1-28.9 89.5-44.2 140-44.2s98.9 15.3 140 44.2c19.9 14 37.5 30.8 52.4 49.9c15.1 19.3 27.1 40.7 35.7 63.4l13.8 36.5l37.8 10C846.1 454.5 884 503.8 884 560c0 33.1-12.9 64.3-36.3 87.7a123.07 123.07 0 0 1-87.6 36.3H720c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h40.1C870.5 760 960 670.5 960 560c0-92.7-63.1-170.7-148.6-193.3z"
|
||||
fill="#FFF"/>
|
||||
</svg>
|
7
Wabbajack.App.Blazor/wwwroot/images/icons/discord.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
|
||||
width="1024px" height="1024px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
|
||||
<path d="M9.593 10.971c-.542 0-.969.475-.969 1.055c0 .578.437 1.055.969 1.055c.541 0 .968-.477.968-1.055c.011-.581-.427-1.055-.968-1.055zm3.468 0c-.542 0-.969.475-.969 1.055c0 .578.437 1.055.969 1.055c.541 0 .968-.477.968-1.055c-.001-.581-.427-1.055-.968-1.055z"
|
||||
fill="#FFF"/>
|
||||
<path d="M17.678 3H4.947A1.952 1.952 0 0 0 3 4.957v12.844c0 1.083.874 1.957 1.947 1.957H15.72l-.505-1.759l1.217 1.131l1.149 1.064L19.625 22V4.957A1.952 1.952 0 0 0 17.678 3zM14.01 15.407s-.342-.408-.626-.771c1.244-.352 1.719-1.13 1.719-1.13c-.39.256-.76.438-1.093.562a6.679 6.679 0 0 1-3.838.398a7.944 7.944 0 0 1-1.396-.41a5.402 5.402 0 0 1-.693-.321c-.029-.021-.057-.029-.085-.048a.117.117 0 0 1-.039-.03c-.171-.094-.266-.16-.266-.16s.456.76 1.663 1.121c-.285.36-.637.789-.637.789c-2.099-.067-2.896-1.444-2.896-1.444c0-3.059 1.368-5.538 1.368-5.538c1.368-1.027 2.669-.998 2.669-.998l.095.114c-1.71.495-2.499 1.245-2.499 1.245s.21-.114.561-.275c1.016-.446 1.823-.57 2.156-.599c.057-.009.105-.019.162-.019a7.756 7.756 0 0 1 4.778.893s-.751-.712-2.366-1.206l.133-.152s1.302-.029 2.669.998c0 0 1.368 2.479 1.368 5.538c0-.001-.807 1.376-2.907 1.443z"
|
||||
fill="#FFF"/>
|
||||
</svg>
|
8
Wabbajack.App.Blazor/wwwroot/images/icons/disk.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
|
||||
width="1024px" height="1024px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 36 36">
|
||||
<path class="clr-i-outline clr-i-outline-path-1"
|
||||
d="M34 21.08L30.86 8.43A2 2 0 0 0 28.94 7H7.06a2 2 0 0 0-1.93 1.47L2 21.08a1 1 0 0 0 0 .24V29a2 2 0 0 0 2 2h28a2 2 0 0 0 2-2v-7.69a1 1 0 0 0 0-.23zM4 29v-7.56L7.06 9h21.87L32 21.44V29z"
|
||||
fill="#FFF"/>
|
||||
<path class="clr-i-outline clr-i-outline-path-2" d="M6 20h24v2H6z" fill="#FFF"/>
|
||||
<path class="clr-i-outline clr-i-outline-path-3" d="M26 24h4v2h-4z" fill="#FFF"/>
|
||||
</svg>
|
8
Wabbajack.App.Blazor/wwwroot/images/icons/github.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
|
||||
width="1024px" height="1024px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
|
||||
<g fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385c.6.105.825-.255.825-.57c0-.285-.015-1.23-.015-2.235c-3.015.555-3.795-.735-4.035-1.41c-.135-.345-.72-1.41-1.23-1.695c-.42-.225-1.02-.78-.015-.795c.945-.015 1.62.87 1.845 1.23c1.08 1.815 2.805 1.305 3.495.99c.105-.78.42-1.305.765-1.605c-2.67-.3-5.46-1.335-5.46-5.925c0-1.305.465-2.385 1.23-3.225c-.12-.3-.54-1.53.12-3.18c0 0 1.005-.315 3.3 1.23c.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23c.66 1.65.24 2.88.12 3.18c.765.84 1.23 1.905 1.23 3.225c0 4.605-2.805 5.625-5.475 5.925c.435.375.81 1.095.81 2.22c0 1.605-.015 2.895-.015 3.3c0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z"
|
||||
fill="#FFF"/>
|
||||
</g>
|
||||
</svg>
|
BIN
Wabbajack.App.Blazor/wwwroot/images/icons/gog.png
Normal file
After Width: | Height: | Size: 118 KiB |
7
Wabbajack.App.Blazor/wwwroot/images/icons/info.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
|
||||
width="1024px" height="1024px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 1024 1024">
|
||||
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448s448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372s372 166.6 372 372s-166.6 372-372 372z"
|
||||
fill="#FFF"/>
|
||||
<path d="M464 336a48 48 0 1 0 96 0a48 48 0 1 0-96 0zm72 112h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z"
|
||||
fill="#FFF"/>
|
||||
</svg>
|
5
Wabbajack.App.Blazor/wwwroot/images/icons/install.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
|
||||
width="1024" height="1024" preserveAspectRatio="xMidYMid meet" viewBox="0 0 1024 1024">
|
||||
<path d="M505.7 661a8 8 0 0 0 12.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
|
||||
fill="#FFF"/>
|
||||
</svg>
|
5
Wabbajack.App.Blazor/wwwroot/images/icons/patreon.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
|
||||
width="1024px" height="1024px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
|
||||
<circle cx="14.508" cy="9.831" r="6.496" fill="#FFF"/>
|
||||
<path d="M2.996 3.335H6.17v17.33H2.996z" fill="#FFF"/>
|
||||
</svg>
|
7
Wabbajack.App.Blazor/wwwroot/images/icons/play.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
|
||||
width="1024px" height="1024px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 1024 1024">
|
||||
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448s448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372s372 166.6 372 372s-166.6 372-372 372z"
|
||||
fill="#FFF"/>
|
||||
<path d="M719.4 499.1l-296.1-215A15.9 15.9 0 0 0 398 297v430c0 13.1 14.8 20.5 25.3 12.9l296.1-215a15.9 15.9 0 0 0 0-25.8zm-257.6 134V390.9L628.5 512L461.8 633.1z"
|
||||
fill="#FFF"/>
|
||||
</svg>
|
9
Wabbajack.App.Blazor/wwwroot/images/icons/settings.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
|
||||
width="1024px" height="1024px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32">
|
||||
<path
|
||||
d="M27 16.76V16v-.77l1.92-1.68A2 2 0 0 0 29.3 11l-2.36-4a2 2 0 0 0-1.73-1a2 2 0 0 0-.64.1l-2.43.82a11.35 11.35 0 0 0-1.31-.75l-.51-2.52a2 2 0 0 0-2-1.61h-4.68a2 2 0 0 0-2 1.61l-.51 2.52a11.48 11.48 0 0 0-1.32.75l-2.38-.86A2 2 0 0 0 6.79 6a2 2 0 0 0-1.73 1L2.7 11a2 2 0 0 0 .41 2.51L5 15.24v1.53l-1.89 1.68A2 2 0 0 0 2.7 21l2.36 4a2 2 0 0 0 1.73 1a2 2 0 0 0 .64-.1l2.43-.82a11.35 11.35 0 0 0 1.31.75l.51 2.52a2 2 0 0 0 2 1.61h4.72a2 2 0 0 0 2-1.61l.51-2.52a11.48 11.48 0 0 0 1.32-.75l2.42.82a2 2 0 0 0 .64.1a2 2 0 0 0 1.73-1l2.28-4a2 2 0 0 0-.41-2.51zM25.21 24l-3.43-1.16a8.86 8.86 0 0 1-2.71 1.57L18.36 28h-4.72l-.71-3.55a9.36 9.36 0 0 1-2.7-1.57L6.79 24l-2.36-4l2.72-2.4a8.9 8.9 0 0 1 0-3.13L4.43 12l2.36-4l3.43 1.16a8.86 8.86 0 0 1 2.71-1.57L13.64 4h4.72l.71 3.55a9.36 9.36 0 0 1 2.7 1.57L25.21 8l2.36 4l-2.72 2.4a8.9 8.9 0 0 1 0 3.13L27.57 20z"
|
||||
fill="#FFF"/>
|
||||
<path
|
||||
d="M16 22a6 6 0 1 1 6-6a5.94 5.94 0 0 1-6 6zm0-10a3.91 3.91 0 0 0-4 4a3.91 3.91 0 0 0 4 4a3.91 3.91 0 0 0 4-4a3.91 3.91 0 0 0-4-4z"
|
||||
fill="#FFF"/>
|
||||
</svg>
|
BIN
Wabbajack.App.Blazor/wwwroot/images/icons/steam.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/icons/wabbajack.ico
Normal file
After Width: | Height: | Size: 139 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/middle_mouse_button.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
Wabbajack.App.Blazor/wwwroot/images/modlist-image.png
Normal file
After Width: | Height: | Size: 2.7 MiB |
37
Wabbajack.App.Blazor/wwwroot/index.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
|
||||
<title>Wabbajack</title>
|
||||
<base href="/"/>
|
||||
<link href="Wabbajack.App.Blazor.styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
html {
|
||||
height: 100%;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
}
|
||||
body
|
||||
{
|
||||
height: inherit;
|
||||
}
|
||||
#app {
|
||||
height: inherit;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="_framework/blazor.webview.js"></script>
|
||||
<script src="_content/Fluxor.Blazor.Web/scripts/index.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -6,6 +6,7 @@ namespace Wabbajack;
|
||||
|
||||
public static class Consts
|
||||
{
|
||||
public static RelativePath MO2IniName = "ModOrganizer.ini".ToRelativePath();
|
||||
public static string AppName = "Wabbajack";
|
||||
public static Uri WabbajackBuildServerUri => new("https://build.wabbajack.org");
|
||||
public static Version CurrentMinimumWabbajackVersion { get; set; } = Version.Parse("2.3.0.0");
|
||||
|
@ -38,7 +38,9 @@ public class LoggerProvider : ILoggerProvider
|
||||
|
||||
_disposables = new CompositeDisposable();
|
||||
|
||||
Messages.Subscribe(m => _messageLog.AddOrUpdate(m))
|
||||
Messages
|
||||
.ObserveOnGuiThread()
|
||||
.Subscribe(m => _messageLog.AddOrUpdate(m))
|
||||
.DisposeWith(_disposables);
|
||||
|
||||
Messages.Subscribe(m => LogToFile(m))
|
||||
|
@ -7,12 +7,15 @@ using System.Reactive.Subjects;
|
||||
using System.Timers;
|
||||
using DynamicData;
|
||||
using DynamicData.Kernel;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.RateLimiter;
|
||||
|
||||
namespace Wabbajack.Models;
|
||||
|
||||
public class ResourceMonitor : IDisposable
|
||||
{
|
||||
private readonly TimeSpan PollInterval = TimeSpan.FromMilliseconds(1000);
|
||||
|
||||
private readonly IResource[] _resources;
|
||||
private readonly Timer _timer;
|
||||
|
||||
@ -33,30 +36,26 @@ public class ResourceMonitor : IDisposable
|
||||
{
|
||||
_compositeDisposable = new CompositeDisposable();
|
||||
_resources = resources.ToArray();
|
||||
_timer = new Timer();
|
||||
_timer.Interval = 250;
|
||||
_timer.Elapsed += Elapsed;
|
||||
_timer.Enabled = true;
|
||||
|
||||
_timer.DisposeWith(_compositeDisposable);
|
||||
|
||||
_prev = _resources.Select(x => (x.Name, (long)0)).ToArray();
|
||||
|
||||
|
||||
RxApp.MainThreadScheduler.ScheduleRecurringAction(PollInterval, Elapsed)
|
||||
.DisposeWith(_compositeDisposable);
|
||||
|
||||
_tasks.Connect()
|
||||
.Bind(out _tasksFiltered)
|
||||
.Subscribe()
|
||||
.DisposeWith(_compositeDisposable);
|
||||
}
|
||||
|
||||
private void Elapsed(object? sender, ElapsedEventArgs e)
|
||||
private void Elapsed()
|
||||
{
|
||||
var current = _resources.Select(x => (x.Name, x.StatusReport.Transferred)).ToArray();
|
||||
var diff = _prev.Zip(current)
|
||||
.Select(t => (t.First.Name, (long)((t.Second.Transferred - t.First.Throughput) / (_timer.Interval / 1000.0))))
|
||||
.Select(t => (t.First.Name, (long)((t.Second.Transferred - t.First.Throughput) / PollInterval.TotalSeconds)))
|
||||
.ToArray();
|
||||
_prev = current;
|
||||
_updates.OnNext(diff);
|
||||
|
||||
|
||||
_tasks.Edit(l =>
|
||||
{
|
||||
var used = new HashSet<ulong>();
|
||||
@ -71,7 +70,7 @@ public class ResourceMonitor : IDisposable
|
||||
{
|
||||
var t = tsk.Value;
|
||||
t.Msg = job.Description;
|
||||
t.ProgressPercent = Percent.FactoryPutInRange(job.Current, (long)job.Size);
|
||||
t.ProgressPercent = job.Size == 0 ? Percent.Zero : Percent.FactoryPutInRange(job.Current, (long)job.Size);
|
||||
}
|
||||
|
||||
// Create
|
||||
@ -82,7 +81,7 @@ public class ResourceMonitor : IDisposable
|
||||
ID = job.ID,
|
||||
StartTime = DateTime.Now,
|
||||
Msg = job.Description,
|
||||
ProgressPercent = Percent.FactoryPutInRange(job.Current, (long) job.Size)
|
||||
ProgressPercent = job.Size == 0 ? Percent.Zero : Percent.FactoryPutInRange(job.Current, (long) job.Size)
|
||||
};
|
||||
l.AddOrUpdate(vm);
|
||||
}
|
||||
|
@ -87,14 +87,6 @@ namespace Wabbajack
|
||||
public bool AutomaticallyOverrideExistingInstall { get; set; }
|
||||
}
|
||||
|
||||
[JsonName("CompilerSettings")]
|
||||
public class CompilerSettings
|
||||
{
|
||||
public ModManager LastCompiledModManager { get; set; }
|
||||
public AbsolutePath OutputLocation { get; set; }
|
||||
public MO2CompilationSettings MO2Compilation { get; } = new MO2CompilationSettings();
|
||||
}
|
||||
|
||||
[JsonName("FiltersSettings")]
|
||||
[JsonObject(MemberSerialization.OptOut)]
|
||||
public class FiltersSettings : ViewModel
|
||||
|
@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -10,16 +12,299 @@ using Wabbajack.RateLimiter;
|
||||
using ReactiveUI;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Compiler;
|
||||
using Wabbajack.Downloaders;
|
||||
using Wabbajack.Downloaders.GameFile;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.DTOs.Interventions;
|
||||
using Wabbajack.DTOs.JsonConverters;
|
||||
using Wabbajack.Installer;
|
||||
using Wabbajack.Models;
|
||||
using Wabbajack.Networking.WabbajackClientApi;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.Paths.IO;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
using Wabbajack.VFS;
|
||||
|
||||
namespace Wabbajack
|
||||
{
|
||||
public class CompilerVM : BackNavigatingVM
|
||||
|
||||
|
||||
public enum CompilerState
|
||||
{
|
||||
public CompilerVM(ILogger<CompilerVM> logger) : base(logger)
|
||||
Configuration,
|
||||
Compiling,
|
||||
Completed,
|
||||
Errored
|
||||
}
|
||||
public class CompilerVM : BackNavigatingVM, ICpuStatusVM
|
||||
{
|
||||
private const string LastSavedCompilerSettings = "last-saved-compiler-settings";
|
||||
private readonly DTOSerializer _dtos;
|
||||
private readonly SettingsManager _settingsManager;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger<CompilerVM> _logger;
|
||||
private readonly ResourceMonitor _resourceMonitor;
|
||||
|
||||
[Reactive]
|
||||
public CompilerState State { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public ISubCompilerVM SubCompilerVM { get; set; }
|
||||
|
||||
// Paths
|
||||
public FilePickerVM ModlistLocation { get; }
|
||||
public FilePickerVM DownloadLocation { get; }
|
||||
public FilePickerVM OutputLocation { get; }
|
||||
|
||||
// Modlist Settings
|
||||
|
||||
[Reactive] public string ModListName { get; set; }
|
||||
[Reactive] public string Version { get; set; }
|
||||
[Reactive] public string Author { get; set; }
|
||||
[Reactive] public string Description { get; set; }
|
||||
public FilePickerVM ModListImagePath { get; } = new();
|
||||
[Reactive] public ImageSource ModListImage { get; set; }
|
||||
[Reactive] public string Website { get; set; }
|
||||
[Reactive] public string Readme { get; set; }
|
||||
[Reactive] public bool IsNSFW { get; set; }
|
||||
[Reactive] public bool PublishUpdate { get; set; }
|
||||
[Reactive] public string MachineUrl { get; set; }
|
||||
[Reactive] public Game BaseGame { get; set; }
|
||||
[Reactive] public string SelectedProfile { get; set; }
|
||||
[Reactive] public AbsolutePath GamePath { get; set; }
|
||||
[Reactive] public bool IsMO2Compilation { get; set; }
|
||||
|
||||
[Reactive] public RelativePath[] AlwaysEnabled { get; set; } = Array.Empty<RelativePath>();
|
||||
[Reactive] public string[] OtherProfiles { get; set; } = Array.Empty<string>();
|
||||
|
||||
[Reactive] public AbsolutePath Source { get; set; }
|
||||
|
||||
public AbsolutePath SettingsOutputLocation => Source.Combine(ModListName).WithExtension(Ext.CompilerSettings);
|
||||
|
||||
|
||||
public ReactiveCommand<Unit, Unit> ExecuteCommand { get; }
|
||||
|
||||
public LoggerProvider LoggerProvider { get; }
|
||||
public ReadOnlyObservableCollection<CPUDisplayVM> StatusList => _resourceMonitor.Tasks;
|
||||
|
||||
public CompilerVM(ILogger<CompilerVM> logger, DTOSerializer dtos, SettingsManager settingsManager,
|
||||
IServiceProvider serviceProvider, LoggerProvider loggerProvider, ResourceMonitor resourceMonitor) : base(logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_dtos = dtos;
|
||||
_settingsManager = settingsManager;
|
||||
_serviceProvider = serviceProvider;
|
||||
LoggerProvider = loggerProvider;
|
||||
_resourceMonitor = resourceMonitor;
|
||||
|
||||
BackCommand =
|
||||
ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await SaveSettingsFile();
|
||||
NavigateToGlobal.Send(NavigateToGlobal.ScreenType.ModeSelectionView);
|
||||
});
|
||||
|
||||
SubCompilerVM = new MO2CompilerVM(this);
|
||||
|
||||
ExecuteCommand = ReactiveCommand.CreateFromTask(async () => await StartCompilation());
|
||||
|
||||
ModlistLocation = new FilePickerVM()
|
||||
{
|
||||
ExistCheckOption = FilePickerVM.CheckOptions.On,
|
||||
PathType = FilePickerVM.PathTypeOptions.File,
|
||||
PromptTitle = "Select a config file or a modlist.txt file"
|
||||
};
|
||||
|
||||
DownloadLocation = new FilePickerVM()
|
||||
{
|
||||
ExistCheckOption = FilePickerVM.CheckOptions.On,
|
||||
PathType = FilePickerVM.PathTypeOptions.Folder,
|
||||
PromptTitle = "Location where the downloads for this list are stored"
|
||||
};
|
||||
|
||||
OutputLocation = new FilePickerVM()
|
||||
{
|
||||
ExistCheckOption = FilePickerVM.CheckOptions.On,
|
||||
PathType = FilePickerVM.PathTypeOptions.Folder,
|
||||
PromptTitle = "Location where the compiled modlist will be stored"
|
||||
};
|
||||
|
||||
ModlistLocation.Filters.AddRange(new []
|
||||
{
|
||||
new CommonFileDialogFilter("MO2 Modlist", "*" + Ext.Txt),
|
||||
new CommonFileDialogFilter("Compiler Settings File", "*" + Ext.CompilerSettings)
|
||||
});
|
||||
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
State = CompilerState.Configuration;
|
||||
Disposable.Empty.DisposeWith(disposables);
|
||||
|
||||
ModlistLocation.WhenAnyValue(vm => vm.TargetPath)
|
||||
.Subscribe(p => InferModListFromLocation(p).FireAndForget())
|
||||
.DisposeWith(disposables);
|
||||
|
||||
LoadLastSavedSettings().FireAndForget();
|
||||
});
|
||||
}
|
||||
|
||||
private async Task InferModListFromLocation(AbsolutePath settingsFile)
|
||||
{
|
||||
if (settingsFile == default) return;
|
||||
|
||||
using var ll = LoadingLock.WithLoading();
|
||||
if (settingsFile.FileName == "modlist.txt".ToRelativePath() && settingsFile.Depth > 3)
|
||||
{
|
||||
var mo2Folder = settingsFile.Parent.Parent.Parent;
|
||||
var mo2Ini = mo2Folder.Combine(Consts.MO2IniName);
|
||||
if (mo2Ini.FileExists())
|
||||
{
|
||||
var iniData = mo2Ini.LoadIniFile();
|
||||
|
||||
var general = iniData["General"];
|
||||
|
||||
BaseGame = GameRegistry.GetByFuzzyName(general["gameName"].FromMO2Ini()).Game;
|
||||
Source = mo2Folder;
|
||||
|
||||
SelectedProfile = general["selected_profile"].FromMO2Ini();
|
||||
GamePath = general["gamePath"].FromMO2Ini().ToAbsolutePath();
|
||||
ModListName = SelectedProfile;
|
||||
|
||||
var settings = iniData["Settings"];
|
||||
var downloadLocation = settings["download_directory"].FromMO2Ini().ToAbsolutePath();
|
||||
|
||||
if (downloadLocation == default)
|
||||
downloadLocation = Source.Combine("downloads");
|
||||
|
||||
DownloadLocation.TargetPath = downloadLocation;
|
||||
IsMO2Compilation = true;
|
||||
|
||||
|
||||
|
||||
AlwaysEnabled = Array.Empty<RelativePath>();
|
||||
// Find Always Enabled mods
|
||||
foreach (var modFolder in mo2Folder.Combine("mods").EnumerateDirectories())
|
||||
{
|
||||
var iniFile = modFolder.Combine("meta.ini");
|
||||
if (!iniFile.FileExists()) continue;
|
||||
|
||||
var data = iniFile.LoadIniFile();
|
||||
var generalModData = data["General"];
|
||||
if ((generalModData["notes"]?.Contains("WABBAJACK_ALWAYS_ENABLE") ?? false) ||
|
||||
(generalModData["comments"]?.Contains("WABBAJACK_ALWAYS_ENABLE") ?? false))
|
||||
AlwaysEnabled = AlwaysEnabled.Append(modFolder.RelativeTo(mo2Folder)).ToArray();
|
||||
}
|
||||
|
||||
var otherProfilesFile = settingsFile.Parent.Combine("otherprofiles.txt");
|
||||
if (otherProfilesFile.FileExists())
|
||||
{
|
||||
OtherProfiles = await otherProfilesFile.ReadAllLinesAsync().ToArray();
|
||||
}
|
||||
|
||||
if (mo2Folder.Depth > 1)
|
||||
OutputLocation.TargetPath = mo2Folder.Parent;
|
||||
|
||||
await SaveSettingsFile();
|
||||
ModlistLocation.TargetPath = SettingsOutputLocation;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task StartCompilation()
|
||||
{
|
||||
var tsk = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
State = CompilerState.Compiling;
|
||||
|
||||
var mo2Settings = new MO2CompilerSettings
|
||||
{
|
||||
Game = BaseGame,
|
||||
ModListName = ModListName,
|
||||
ModListAuthor = Author,
|
||||
ModlistReadme = Readme,
|
||||
Source = Source,
|
||||
Downloads = DownloadLocation.TargetPath,
|
||||
OutputFile = OutputLocation.TargetPath,
|
||||
Profile = SelectedProfile,
|
||||
OtherProfiles = OtherProfiles,
|
||||
AlwaysEnabled = AlwaysEnabled
|
||||
};
|
||||
|
||||
var compiler = new MO2Compiler(_serviceProvider.GetRequiredService<ILogger<MO2Compiler>>(),
|
||||
_serviceProvider.GetRequiredService<FileExtractor.FileExtractor>(),
|
||||
_serviceProvider.GetRequiredService<FileHashCache>(),
|
||||
_serviceProvider.GetRequiredService<Context>(),
|
||||
_serviceProvider.GetRequiredService<TemporaryFileManager>(),
|
||||
mo2Settings,
|
||||
_serviceProvider.GetRequiredService<ParallelOptions>(),
|
||||
_serviceProvider.GetRequiredService<DownloadDispatcher>(),
|
||||
_serviceProvider.GetRequiredService<Client>(),
|
||||
_serviceProvider.GetRequiredService<IGameLocator>(),
|
||||
_serviceProvider.GetRequiredService<DTOSerializer>(),
|
||||
_serviceProvider.GetRequiredService<IResource<ACompiler>>(),
|
||||
_serviceProvider.GetRequiredService<IBinaryPatchCache>());
|
||||
|
||||
await compiler.Begin(CancellationToken.None);
|
||||
|
||||
State = CompilerState.Completed;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
State = CompilerState.Errored;
|
||||
_logger.LogInformation(ex, "Failed Compilation : {Message}", ex.Message);
|
||||
}
|
||||
});
|
||||
|
||||
await tsk;
|
||||
}
|
||||
|
||||
private async Task SaveSettingsFile()
|
||||
{
|
||||
if (Source == default) return;
|
||||
await using var st = SettingsOutputLocation.Open(FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
await JsonSerializer.SerializeAsync(st, GetSettings(), _dtos.Options);
|
||||
|
||||
await _settingsManager.Save(LastSavedCompilerSettings, Source);
|
||||
}
|
||||
|
||||
private async Task LoadLastSavedSettings()
|
||||
{
|
||||
var lastPath = await _settingsManager.Load<AbsolutePath>(LastSavedCompilerSettings);
|
||||
if (Source == default) return;
|
||||
Source = lastPath;
|
||||
}
|
||||
|
||||
|
||||
private CompilerSettings GetSettings()
|
||||
{
|
||||
return new CompilerSettings
|
||||
{
|
||||
ModListName = ModListName,
|
||||
ModListAuthor = Author,
|
||||
Downloads = DownloadLocation.TargetPath,
|
||||
Source = ModlistLocation.TargetPath,
|
||||
Game = BaseGame,
|
||||
Profile = SelectedProfile,
|
||||
UseGamePaths = true,
|
||||
OutputFile = OutputLocation.TargetPath.Combine(SelectedProfile).WithExtension(Ext.Wabbajack),
|
||||
AlwaysEnabled = AlwaysEnabled.ToArray(),
|
||||
OtherProfiles = OtherProfiles.ToArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,40 +2,28 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using ReactiveUI;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Media.Imaging;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using System.Windows.Media;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using System.Reactive;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Shell;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||
using Microsoft.WindowsAPICodePack.Shell;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Downloaders.GameFile;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.DTOs.JsonConverters;
|
||||
using Wabbajack.Extensions;
|
||||
using Wabbajack.Hashing.xxHash64;
|
||||
using Wabbajack.Installer;
|
||||
using Wabbajack.Interventions;
|
||||
using Wabbajack.Messages;
|
||||
using Wabbajack.Models;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.RateLimiter;
|
||||
using Wabbajack.View_Models;
|
||||
using Wabbajack.Paths.IO;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
using Wabbajack.Util;
|
||||
using Configuration = Wabbajack.Networking.WabbajackClientApi.Configuration;
|
||||
using Consts = Wabbajack.Consts;
|
||||
using KnownFolders = Wabbajack.Paths.IO.KnownFolders;
|
||||
|
||||
namespace Wabbajack;
|
||||
|
||||
@ -118,8 +106,7 @@ public class InstallerVM : BackNavigatingVM, IBackNavigatingVM, ICpuStatusVM
|
||||
[Reactive]
|
||||
public bool Installing { get; set; }
|
||||
|
||||
[Reactive]
|
||||
public LoggerProvider LoggerProvider { get; set; }
|
||||
public LoggerProvider LoggerProvider { get; }
|
||||
|
||||
|
||||
// Command properties
|
||||
|
@ -166,31 +166,7 @@
|
||||
<ColumnDefinition Width="20" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Grid.Row="0" Grid.RowSpan="5" Grid.Column="0"
|
||||
Margin="15"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<local:ImageRadioButtonView Grid.Row="0"
|
||||
x:Name="MO2CompilerButton"
|
||||
Height="35"
|
||||
Margin="4"
|
||||
IsChecked="{Binding SelectedCompilerType, Converter={StaticResource EqualsToBoolConverter}, ConverterParameter={x:Static local:ModManager.Standard}}">
|
||||
<local:ImageRadioButtonView.Image>
|
||||
<BitmapImage UriSource="../../Resources/MO2Button.png" />
|
||||
</local:ImageRadioButtonView.Image>
|
||||
</local:ImageRadioButtonView>
|
||||
</Grid>
|
||||
<ContentPresenter Grid.Row="1" Grid.Column="1"
|
||||
x:Name="CustomCompilerSettingsPresenter">
|
||||
<ContentPresenter.Resources>
|
||||
<DataTemplate DataType="{x:Type local:MO2CompilerVM}">
|
||||
<local:MO2CompilerConfigView />
|
||||
</DataTemplate>
|
||||
</ContentPresenter.Resources>
|
||||
</ContentPresenter>
|
||||
<local:MO2CompilerConfigView x:Name="CompilerConfigView" Grid.Row="1" Grid.Column="1" />
|
||||
<local:BeginButton Grid.Row="0" Grid.RowSpan="3" Grid.Column="5"
|
||||
x:Name="BeginButton" />
|
||||
</Grid>
|
||||
|