Lots of code cleanup
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"isRoot": true,
|
|
||||||
"tools": {
|
|
||||||
"excubo.webcompiler": {
|
|
||||||
"version": "2.7.14",
|
|
||||||
"commands": [
|
|
||||||
"webcompiler"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
# All files
|
|
||||||
[*]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.scss]
|
|
||||||
indent_size = 2
|
|
3
Wabbajack.App.Blazor/.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
.sonarqube
|
|
||||||
**/*.css
|
|
||||||
**/*.css.map
|
|
@ -1,15 +0,0 @@
|
|||||||
<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>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
|
|
||||||
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
|
|
||||||
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Dark.Purple.xaml" />
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
</ResourceDictionary>
|
|
||||||
</Application.Resources>
|
|
||||||
</Application>
|
|
@ -1,95 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Windows;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using NLog.Extensions.Logging;
|
|
||||||
using NLog.Targets;
|
|
||||||
using Wabbajack.App.Blazor.State;
|
|
||||||
using Wabbajack.App.Blazor.Utility;
|
|
||||||
using Wabbajack.Services.OSIntegrated;
|
|
||||||
using Blazored.Modal;
|
|
||||||
using Blazored.Toast;
|
|
||||||
using Wabbajack.App.Blazor.Browser.ViewModels;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor;
|
|
||||||
|
|
||||||
public partial class App
|
|
||||||
{
|
|
||||||
private readonly IServiceProvider _serviceProvider;
|
|
||||||
|
|
||||||
public App()
|
|
||||||
{
|
|
||||||
_serviceProvider = Host.CreateDefaultBuilder(Array.Empty<string>())
|
|
||||||
.ConfigureLogging(SetupLogging)
|
|
||||||
.ConfigureServices(services => ConfigureServices(services))
|
|
||||||
.Build()
|
|
||||||
.Services;
|
|
||||||
_serviceProvider.GetRequiredService<SystemParametersConstructor>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SetupLogging(ILoggingBuilder loggingBuilder)
|
|
||||||
{
|
|
||||||
var config = new NLog.Config.LoggingConfiguration();
|
|
||||||
|
|
||||||
var fileTarget = new FileTarget("file")
|
|
||||||
{
|
|
||||||
FileName = "logs/Wabbajack.current.log",
|
|
||||||
ArchiveFileName = "logs/Wabbajack.{##}.log",
|
|
||||||
ArchiveOldFileOnStartup = true,
|
|
||||||
MaxArchiveFiles = 10,
|
|
||||||
Layout = "${processtime} [${level:uppercase=true}] (${logger}) ${message:withexception=true}",
|
|
||||||
Header = "############ Wabbajack log file - ${longdate} ############"
|
|
||||||
};
|
|
||||||
|
|
||||||
var consoleTarget = new ConsoleTarget("console");
|
|
||||||
|
|
||||||
var uiTarget = new UiLoggerTarget
|
|
||||||
{
|
|
||||||
Name = "ui",
|
|
||||||
Layout = "${message}",
|
|
||||||
};
|
|
||||||
|
|
||||||
var blackholeTarget = new NullTarget("blackhole");
|
|
||||||
|
|
||||||
if (!string.Equals("TRUE", Environment.GetEnvironmentVariable("DEBUG_BLAZOR", EnvironmentVariableTarget.Process), StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Debug, blackholeTarget, "Microsoft.AspNetCore.Components.*", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
config.AddRuleForAllLevels(fileTarget);
|
|
||||||
config.AddRuleForAllLevels(consoleTarget);
|
|
||||||
config.AddRuleForAllLevels(uiTarget);
|
|
||||||
|
|
||||||
loggingBuilder.ClearProviders();
|
|
||||||
loggingBuilder.SetMinimumLevel(LogLevel.Trace);
|
|
||||||
loggingBuilder.AddNLog(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IServiceCollection ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddOSIntegrated();
|
|
||||||
services.AddBlazorWebView();
|
|
||||||
services.AddBlazoredModal();
|
|
||||||
services.AddBlazoredToast();
|
|
||||||
services.AddTransient<MainWindow>();
|
|
||||||
services.AddTransient<NexusLogin>();
|
|
||||||
services.AddTransient<VectorPlexus>();
|
|
||||||
services.AddTransient<LoversLab>();
|
|
||||||
services.AddTransient<BethesdaNetLogin>();
|
|
||||||
services.AddSingleton<SystemParametersConstructor>();
|
|
||||||
services.AddSingleton<IStateContainer, StateContainer>();
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
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)
|
|
||||||
)]
|
|
@ -1,23 +0,0 @@
|
|||||||
<TabItem x:Class="Wabbajack.App.Blazor.Browser.BrowserTabView"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:local="clr-namespace:Wabbajack.App.Blazor.Browser"
|
|
||||||
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="300" d:DesignWidth="300">
|
|
||||||
<TabItem.Style>
|
|
||||||
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}"></Style>
|
|
||||||
</TabItem.Style>
|
|
||||||
<TabItem.Header>
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<TextBlock FontSize="16" x:Name="HeaderText">_</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</TabItem.Header>
|
|
||||||
<Grid>
|
|
||||||
<local:BrowserView x:Name="Browser">
|
|
||||||
</local:BrowserView>
|
|
||||||
</Grid>
|
|
||||||
</TabItem>
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Reactive.Disposables;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using ReactiveUI;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Browser;
|
|
||||||
|
|
||||||
public partial class BrowserTabView : IDisposable
|
|
||||||
{
|
|
||||||
private readonly CompositeDisposable _compositeDisposable;
|
|
||||||
|
|
||||||
public BrowserTabView(BrowserTabViewModel vm)
|
|
||||||
{
|
|
||||||
_compositeDisposable = new CompositeDisposable();
|
|
||||||
InitializeComponent();
|
|
||||||
Browser.Browser.Source = new Uri("http://www.google.com");
|
|
||||||
vm.Browser = Browser;
|
|
||||||
DataContext = vm;
|
|
||||||
|
|
||||||
vm.WhenAnyValue(vm => vm.HeaderText)
|
|
||||||
.BindTo(this, view => view.HeaderText.Text)
|
|
||||||
.DisposeWith(_compositeDisposable);
|
|
||||||
|
|
||||||
Start().FireAndForget();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Start()
|
|
||||||
{
|
|
||||||
await ((BrowserTabViewModel) DataContext).RunWrapper(CancellationToken.None);
|
|
||||||
ClickClose(this, new RoutedEventArgs());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_compositeDisposable.Dispose();
|
|
||||||
var vm = (BrowserTabViewModel) DataContext;
|
|
||||||
vm.Browser = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClickClose(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
var tc = (TabControl) this.Parent;
|
|
||||||
tc.Items.Remove(this);
|
|
||||||
this.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Web;
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
using Microsoft.Web.WebView2.Core;
|
|
||||||
using Microsoft.Web.WebView2.Wpf;
|
|
||||||
using ReactiveUI.Fody.Helpers;
|
|
||||||
using Wabbajack.DTOs.Logins;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Browser;
|
|
||||||
|
|
||||||
public abstract class BrowserTabViewModel : ViewModel
|
|
||||||
{
|
|
||||||
[Reactive]
|
|
||||||
public string HeaderText { get; set; }
|
|
||||||
|
|
||||||
[Reactive]
|
|
||||||
public string Instructions { get; set; }
|
|
||||||
|
|
||||||
public BrowserView? Browser { get; set; }
|
|
||||||
|
|
||||||
private WebView2 _browser => Browser!.Browser;
|
|
||||||
|
|
||||||
public async Task RunWrapper(CancellationToken token)
|
|
||||||
{
|
|
||||||
await Run(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract Task Run(CancellationToken token);
|
|
||||||
|
|
||||||
protected async Task WaitForReady()
|
|
||||||
{
|
|
||||||
while (Browser?.Browser.CoreWebView2 == null)
|
|
||||||
{
|
|
||||||
await Task.Delay(250);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task NavigateTo(Uri uri)
|
|
||||||
{
|
|
||||||
var tcs = new TaskCompletionSource();
|
|
||||||
|
|
||||||
void Completed(object? o, CoreWebView2NavigationCompletedEventArgs a)
|
|
||||||
{
|
|
||||||
if (a.IsSuccess)
|
|
||||||
{
|
|
||||||
tcs.TrySetResult();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tcs.TrySetException(new Exception($"Navigation error to {uri}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_browser.NavigationCompleted += Completed;
|
|
||||||
_browser.Source = uri;
|
|
||||||
await tcs.Task;
|
|
||||||
_browser.NavigationCompleted -= Completed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Cookie[]> GetCookies(string domainEnding, CancellationToken token)
|
|
||||||
{
|
|
||||||
var cookies = (await _browser.CoreWebView2.CookieManager.GetCookiesAsync(""))
|
|
||||||
.Where(c => c.Domain.EndsWith(domainEnding));
|
|
||||||
return cookies.Select(c => new Cookie
|
|
||||||
{
|
|
||||||
Domain = c.Domain,
|
|
||||||
Name = c.Name,
|
|
||||||
Path = c.Path,
|
|
||||||
Value = c.Value
|
|
||||||
}).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> EvaluateJavaScript(string js)
|
|
||||||
{
|
|
||||||
return await _browser.ExecuteScriptAsync(js);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<HtmlDocument> GetDom(CancellationToken token)
|
|
||||||
{
|
|
||||||
var v = HttpUtility.UrlDecode("\u003D");
|
|
||||||
var source = await EvaluateJavaScript("document.body.outerHTML");
|
|
||||||
var decoded = JsonSerializer.Deserialize<string>(source);
|
|
||||||
var doc = new HtmlDocument();
|
|
||||||
doc.LoadHtml(decoded);
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
<reactiveUi:ReactiveUserControl x:TypeArguments="local:BrowserTabViewModel"
|
|
||||||
x:Class="Wabbajack.App.Blazor.Browser.BrowserView"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:local="clr-namespace:Wabbajack.App.Blazor.Browser"
|
|
||||||
xmlns:wpf="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
|
|
||||||
xmlns:reactiveUi="http://reactiveui.net"
|
|
||||||
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="300" d:DesignWidth="300">
|
|
||||||
<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="30"></ColumnDefinition>
|
|
||||||
<ColumnDefinition Width="30"></ColumnDefinition>
|
|
||||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="30"></RowDefinition>
|
|
||||||
<RowDefinition Height="*"></RowDefinition>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<Button Grid.Row="0" Grid.Column="0">
|
|
||||||
<iconPacks:PackIconModern Kind="NavigatePrevious"></iconPacks:PackIconModern>
|
|
||||||
</Button>
|
|
||||||
<Button Grid.Row="0" Grid.Column="1">
|
|
||||||
<iconPacks:PackIconModern Kind="Home"></iconPacks:PackIconModern>
|
|
||||||
</Button>
|
|
||||||
<TextBox Grid.Row="0" Grid.Column="3" VerticalContentAlignment="Center"></TextBox>
|
|
||||||
<wpf:WebView2 Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" x:Name="Browser"></wpf:WebView2>
|
|
||||||
</Grid>
|
|
||||||
</reactiveUi:ReactiveUserControl>
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
|||||||
using System.Windows.Controls;
|
|
||||||
using ReactiveUI;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Browser;
|
|
||||||
|
|
||||||
public partial class BrowserView
|
|
||||||
{
|
|
||||||
public BrowserView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
|||||||
using ReactiveUI;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Browser;
|
|
||||||
|
|
||||||
public class ViewModel : ReactiveObject, IActivatableViewModel
|
|
||||||
{
|
|
||||||
public ViewModelActivator Activator { get; } = new();
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Web.WebView2.Core;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
using Wabbajack.Networking.BethesdaNet;
|
|
||||||
using Wabbajack.Services.OSIntegrated;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Browser.ViewModels;
|
|
||||||
|
|
||||||
public class BethesdaNetLogin : BrowserTabViewModel
|
|
||||||
{
|
|
||||||
private readonly EncryptedJsonTokenProvider<Wabbajack.DTOs.Logins.BethesdaNetLoginState> _tokenProvider;
|
|
||||||
private readonly Client _client;
|
|
||||||
|
|
||||||
public BethesdaNetLogin(EncryptedJsonTokenProvider<Wabbajack.DTOs.Logins.BethesdaNetLoginState> tokenProvider, Wabbajack.Networking.BethesdaNet.Client client)
|
|
||||||
{
|
|
||||||
_tokenProvider = tokenProvider;
|
|
||||||
_client = client;
|
|
||||||
HeaderText = "Bethesda Net Login";
|
|
||||||
}
|
|
||||||
protected override async Task Run(CancellationToken token)
|
|
||||||
{
|
|
||||||
await WaitForReady();
|
|
||||||
Instructions = "Please log in to Bethesda.net";
|
|
||||||
|
|
||||||
string requestJson = "";
|
|
||||||
|
|
||||||
Browser.Browser.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);
|
|
||||||
Browser.Browser.CoreWebView2.WebResourceRequested += (sender, args) =>
|
|
||||||
{
|
|
||||||
if (args.Request.Uri == "https://api.bethesda.net/dwemer/attunement/v1/authenticate" && args.Request.Method == "POST")
|
|
||||||
{
|
|
||||||
requestJson = args.Request.Content.ReadAllText();
|
|
||||||
args.Request.Content = new MemoryStream(Encoding.UTF8.GetBytes(requestJson));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await NavigateTo(new Uri("https://bethesda.net/en/dashboard"));
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var code = await GetCookies("bethesda.net", token);
|
|
||||||
if (code.Any(c => c.Name == "bnet-session")) break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = JsonSerializer.Deserialize<LoginRequest>(requestJson);
|
|
||||||
|
|
||||||
var provider = new Wabbajack.DTOs.Logins.BethesdaNetLoginState()
|
|
||||||
{
|
|
||||||
Username = data.UserName,
|
|
||||||
Password = data.Password
|
|
||||||
};
|
|
||||||
await _tokenProvider.SetToken(provider);
|
|
||||||
await _client.Login(token);
|
|
||||||
|
|
||||||
await Task.Delay(10);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LoginRequest
|
|
||||||
{
|
|
||||||
[JsonPropertyName("username")]
|
|
||||||
public string UserName { get; set; }
|
|
||||||
[JsonPropertyName("password")]
|
|
||||||
public string Password { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.Http.Json;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Web;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Wabbajack.DTOs.Logins;
|
|
||||||
using Wabbajack.Services.OSIntegrated;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Browser.ViewModels;
|
|
||||||
|
|
||||||
public abstract class IPSOAuth2Login<TLoginType> : BrowserTabViewModel
|
|
||||||
where TLoginType : OAuth2LoginState, new()
|
|
||||||
{
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
private readonly EncryptedJsonTokenProvider<TLoginType> _tokenProvider;
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
public IPSOAuth2Login(ILogger logger, HttpClient httpClient,
|
|
||||||
EncryptedJsonTokenProvider<TLoginType> tokenProvider)
|
|
||||||
{
|
|
||||||
var tlogin = new TLoginType();
|
|
||||||
HeaderText = $"{tlogin.SiteName} Login";
|
|
||||||
_logger = logger;
|
|
||||||
_httpClient = httpClient;
|
|
||||||
_tokenProvider = tokenProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task Run(CancellationToken token)
|
|
||||||
{
|
|
||||||
var tlogin = new TLoginType();
|
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<Uri>();
|
|
||||||
await WaitForReady();
|
|
||||||
Browser!.Browser.CoreWebView2.Settings.UserAgent = "Wabbajack";
|
|
||||||
Browser!.Browser.NavigationStarting += (sender, args) =>
|
|
||||||
{
|
|
||||||
var uri = new Uri(args.Uri);
|
|
||||||
if (uri.Scheme == "wabbajack")
|
|
||||||
{
|
|
||||||
tcs.TrySetResult(uri);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Instructions = $"Please log in and allow Wabbajack to access your {tlogin.SiteName} account";
|
|
||||||
|
|
||||||
var scopes = string.Join(" ", tlogin.Scopes);
|
|
||||||
var state = Guid.NewGuid().ToString();
|
|
||||||
|
|
||||||
await NavigateTo(new Uri(tlogin.AuthorizationEndpoint +
|
|
||||||
$"?response_type=code&client_id={tlogin.ClientID}&state={state}&scope={scopes}"));
|
|
||||||
|
|
||||||
var uri = await tcs.Task.WaitAsync(token);
|
|
||||||
|
|
||||||
var cookies = await GetCookies(tlogin.AuthorizationEndpoint.Host, token);
|
|
||||||
|
|
||||||
var parsed = HttpUtility.ParseQueryString(uri.Query);
|
|
||||||
if (parsed.Get("state") != state)
|
|
||||||
{
|
|
||||||
_logger.LogCritical("Bad OAuth state, this shouldn't happen");
|
|
||||||
throw new Exception("Bad OAuth State");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsed.Get("code") == null)
|
|
||||||
{
|
|
||||||
_logger.LogCritical("Bad code result from OAuth");
|
|
||||||
throw new Exception("Bad code result from OAuth");
|
|
||||||
}
|
|
||||||
|
|
||||||
var authCode = parsed.Get("code");
|
|
||||||
|
|
||||||
var formData = new KeyValuePair<string?, string?>[]
|
|
||||||
{
|
|
||||||
new("grant_type", "authorization_code"),
|
|
||||||
new("code", authCode),
|
|
||||||
new("client_id", tlogin.ClientID)
|
|
||||||
};
|
|
||||||
|
|
||||||
var msg = new HttpRequestMessage();
|
|
||||||
msg.Method = HttpMethod.Post;
|
|
||||||
msg.RequestUri = tlogin.TokenEndpoint;
|
|
||||||
msg.Headers.Add("User-Agent",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36");
|
|
||||||
msg.Headers.Add("Cookie", string.Join(";", cookies.Select(c => $"{c.Name}={c.Value}")));
|
|
||||||
msg.Content = new FormUrlEncodedContent(formData.ToList());
|
|
||||||
|
|
||||||
using var response = await _httpClient.SendAsync(msg, token);
|
|
||||||
var data = await response.Content.ReadFromJsonAsync<OAuthResultState>(cancellationToken: token);
|
|
||||||
|
|
||||||
await _tokenProvider.SetToken(new TLoginType
|
|
||||||
{
|
|
||||||
Cookies = cookies,
|
|
||||||
ResultState = data!
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
using System.Net.Http;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Wabbajack.DTOs.DownloadStates;
|
|
||||||
using Wabbajack.DTOs.Logins;
|
|
||||||
using Wabbajack.Services.OSIntegrated;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Browser.ViewModels;
|
|
||||||
|
|
||||||
public class LoversLab : IPSOAuth2Login<LoversLabLoginState>
|
|
||||||
{
|
|
||||||
public LoversLab(ILogger<LoversLab> logger, HttpClient httpClient, EncryptedJsonTokenProvider<LoversLabLoginState> tokenProvider)
|
|
||||||
: base(logger, httpClient, tokenProvider)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Fizzler.Systems.HtmlAgilityPack;
|
|
||||||
using Wabbajack.DTOs.Logins;
|
|
||||||
using Wabbajack.Services.OSIntegrated;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Browser.ViewModels;
|
|
||||||
|
|
||||||
public class NexusLogin : BrowserTabViewModel
|
|
||||||
{
|
|
||||||
private readonly EncryptedJsonTokenProvider<NexusApiState> _tokenProvider;
|
|
||||||
|
|
||||||
public NexusLogin(EncryptedJsonTokenProvider<NexusApiState> tokenProvider)
|
|
||||||
{
|
|
||||||
HeaderText = "Nexus Login";
|
|
||||||
_tokenProvider = tokenProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task Run(CancellationToken token)
|
|
||||||
{
|
|
||||||
token.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
Instructions = "Please log into the Nexus";
|
|
||||||
|
|
||||||
await NavigateTo(new Uri(
|
|
||||||
"https://users.nexusmods.com/auth/continue?client_id=nexus&redirect_uri=https://www.nexusmods.com/oauth/callback&response_type=code&referrer=//www.nexusmods.com"));
|
|
||||||
|
|
||||||
|
|
||||||
Cookie[] cookies = { };
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
cookies = await GetCookies("nexusmods.com", token);
|
|
||||||
if (cookies.Any(c => c.Name == "member_id"))
|
|
||||||
break;
|
|
||||||
|
|
||||||
token.ThrowIfCancellationRequested();
|
|
||||||
await Task.Delay(500, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
Instructions = "Getting API Key...";
|
|
||||||
|
|
||||||
await NavigateTo(new Uri("https://www.nexusmods.com/users/myaccount?tab=api"));
|
|
||||||
|
|
||||||
var key = "";
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
key = (await GetDom(token))
|
|
||||||
.DocumentNode
|
|
||||||
.QuerySelectorAll("input[value=wabbajack]")
|
|
||||||
.SelectMany(p => p.ParentNode.ParentNode.QuerySelectorAll("textarea.application-key"))
|
|
||||||
.Select(node => node.InnerHtml)
|
|
||||||
.FirstOrDefault() ?? "";
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(key))
|
|
||||||
break;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await EvaluateJavaScript(
|
|
||||||
"var found = document.querySelector(\"input[value=wabbajack]\").parentElement.parentElement.querySelector(\"form button[type=submit]\");" +
|
|
||||||
"found.onclick= function() {return true;};" +
|
|
||||||
"found.class = \" \"; " +
|
|
||||||
"found.click();" +
|
|
||||||
"found.remove(); found = undefined;"
|
|
||||||
);
|
|
||||||
Instructions = "Generating API Key, Please Wait...";
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
token.ThrowIfCancellationRequested();
|
|
||||||
await Task.Delay(500, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
Instructions = "Success, saving information...";
|
|
||||||
await _tokenProvider.SetToken(new NexusApiState
|
|
||||||
{
|
|
||||||
Cookies = cookies,
|
|
||||||
ApiKey = key
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
using System.Net.Http;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Wabbajack.DTOs.Logins;
|
|
||||||
using Wabbajack.Services.OSIntegrated;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Browser.ViewModels;
|
|
||||||
|
|
||||||
public class VectorPlexus : IPSOAuth2Login<VectorPlexusLoginState>
|
|
||||||
{
|
|
||||||
public VectorPlexus(ILogger<VectorPlexus> logger, HttpClient httpClient, EncryptedJsonTokenProvider<VectorPlexusLoginState> tokenProvider)
|
|
||||||
: base(logger, httpClient, tokenProvider)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
@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; }
|
|
||||||
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
@namespace Wabbajack.App.Blazor.Components
|
|
||||||
|
|
||||||
<div id="info-block">
|
|
||||||
@if (Supertitle is not null)
|
|
||||||
{
|
|
||||||
<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; }
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public string? Title { get; set; }
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public string? Subtitle { get; set; }
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public string? Comment { get; set; }
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public string? Description { get; set; }
|
|
||||||
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
@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; }
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
@namespace Wabbajack.App.Blazor.Components
|
|
||||||
|
|
||||||
<h3>[TBI] Model Component</h3>
|
|
||||||
<p>@Content</p>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter]
|
|
||||||
public string? Content { get; set; }
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
@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; }
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
@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>
|
|
@ -1,31 +0,0 @@
|
|||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
@namespace Wabbajack.App.Blazor.Components
|
|
||||||
|
|
||||||
<label class="option">
|
|
||||||
@Label
|
|
||||||
<input type="checkbox" value="@IsChecked" @onchange="CheckBoxChanged">
|
|
||||||
<span class="checkmark"></span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
// TODO: [Low] Implement parameters to customize style.
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public string? Label { get; set; }
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public bool IsChecked { get; set; }
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public EventCallback<bool> IsCheckedChanged { get; set; }
|
|
||||||
|
|
||||||
private async Task CheckBoxChanged(ChangeEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.Value is not bool newValue) return;
|
|
||||||
if (IsChecked == newValue) return;
|
|
||||||
|
|
||||||
IsChecked = newValue;
|
|
||||||
await IsCheckedChanged.InvokeAsync(IsChecked);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
@using Wabbajack.RateLimiter
|
|
||||||
@using System
|
|
||||||
@using System.Reactive.Linq
|
|
||||||
@implements IDisposable
|
|
||||||
|
|
||||||
@namespace Wabbajack.App.Blazor.Components
|
|
||||||
|
|
||||||
<div id="progress-bar">
|
|
||||||
<progress max="1" value="@CurrentProgress.ToString("F")"></progress>
|
|
||||||
<span class="text">@Text</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter] public IObservable<Percent>? ProgressObserver { get; set; }
|
|
||||||
|
|
||||||
private double CurrentProgress { get; set; }
|
|
||||||
private string Text { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
private IDisposable? _disposable;
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
if (ProgressObserver is null) return;
|
|
||||||
|
|
||||||
_disposable = ProgressObserver
|
|
||||||
.Sample(TimeSpan.FromMilliseconds(250))
|
|
||||||
.DistinctUntilChanged(p => p.Value)
|
|
||||||
.Subscribe(p =>
|
|
||||||
{
|
|
||||||
CurrentProgress = p.Value;
|
|
||||||
Text = p.ToString();
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() => _disposable?.Dispose();
|
|
||||||
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
@using Wabbajack.App.Blazor.Utility
|
|
||||||
@code {
|
|
||||||
private void OpenPatreonPage() => UIUtils.OpenWebsite(new Uri("https://www.patreon.com/user?u=11907933"));
|
|
||||||
private void OpenGithubPage() => UIUtils.OpenWebsite(new Uri("https://github.com/wabbajack-tools/wabbajack"));
|
|
||||||
private void OpenDiscord() => UIUtils.OpenWebsite(new Uri("https://discord.gg/wabbajack"));
|
|
||||||
}
|
|
||||||
@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">
|
|
||||||
@* TODO: wrap social icons in a button and make it clickable *@
|
|
||||||
<img src="images/icons/patreon.svg" alt="" onclick="@OpenPatreonPage">
|
|
||||||
<img src="images/icons/github.svg" alt="" onclick="@OpenGithubPage">
|
|
||||||
<img src="images/icons/discord.svg" alt="" onclick="@OpenDiscord">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,33 +0,0 @@
|
|||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
@using Wabbajack.App.Blazor.Pages
|
|
||||||
@using Wabbajack.App.Blazor.Shared
|
|
||||||
@using Wabbajack.App.Blazor.State
|
|
||||||
@inject NavigationManager _navigationManager
|
|
||||||
@inject IStateContainer _stateContainer
|
|
||||||
|
|
||||||
@namespace Wabbajack.App.Blazor.Components
|
|
||||||
|
|
||||||
<header id="top-bar">
|
|
||||||
<nav class="@(_stateContainer.NavigationAllowed ? "" : "disallow")">
|
|
||||||
<ul>
|
|
||||||
@foreach (var (name, route) in Pages)
|
|
||||||
{
|
|
||||||
<li>
|
|
||||||
<div class="item @CurrentPage(route)" @onclick="@(() => Navigate(route))">@name</div>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
<div class="settings">
|
|
||||||
<InteractionIcon Icon="images/icons/adjust.svg" Label="Settings" Size="100%" OnClick="@(() => Navigate(Settings.Route))"/>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
private static readonly Dictionary<string, string> Pages = new()
|
|
||||||
{
|
|
||||||
{"Play", Play.Route},
|
|
||||||
{"Gallery", Gallery.Route},
|
|
||||||
{"Install", Select.Route},
|
|
||||||
{"Create", Create.Route}
|
|
||||||
};
|
|
||||||
|
|
||||||
private void Navigate(string page)
|
|
||||||
{
|
|
||||||
_navigationManager.NavigateTo(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
// TODO(erri120): update this
|
|
||||||
// _navigationManager.LocationChanged += (_, _) => StateHasChanged();
|
|
||||||
// _globalState.OnNavigationStateChange += StateHasChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string CurrentPage(string page)
|
|
||||||
{
|
|
||||||
var relativePath = _navigationManager.ToBaseRelativePath(_navigationManager.Uri);
|
|
||||||
return page.Equals(relativePath, StringComparison.OrdinalIgnoreCase) ? "active" : string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
@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%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
@using NLog
|
|
||||||
@using NLog.Targets
|
|
||||||
@using Wabbajack.App.Blazor.Utility
|
|
||||||
@using System.Reactive.Linq
|
|
||||||
@namespace Wabbajack.App.Blazor.Components
|
|
||||||
@implements IDisposable
|
|
||||||
|
|
||||||
<div id="virtual-logger">
|
|
||||||
<Virtualize Items="_logs" Context="logItem" OverscanCount="5" ItemSize="24">
|
|
||||||
<span style="height: 24px">@logItem</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?)
|
|
||||||
|
|
||||||
private UiLoggerTarget? _loggerTarget;
|
|
||||||
private ICollection<string> _logs = new List<string>();
|
|
||||||
|
|
||||||
private bool _shouldRender = false;
|
|
||||||
protected override bool ShouldRender() => _shouldRender;
|
|
||||||
|
|
||||||
private IDisposable? _disposable;
|
|
||||||
|
|
||||||
protected override Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
_loggerTarget = LogManager.Configuration.FindTargetByName<UiLoggerTarget>("ui");
|
|
||||||
|
|
||||||
_disposable = _loggerTarget.Logs.Sample(TimeSpan.FromMilliseconds(250)).Subscribe(next =>
|
|
||||||
{
|
|
||||||
_logs.Add(next);
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
});
|
|
||||||
|
|
||||||
_shouldRender = true;
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() => _disposable?.Dispose();
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
|
||||||
<ReactiveUI />
|
|
||||||
</Weavers>
|
|
@ -1,26 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
||||||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
|
||||||
<xs:element name="Weavers">
|
|
||||||
<xs:complexType>
|
|
||||||
<xs:all>
|
|
||||||
<xs:element name="ReactiveUI" minOccurs="0" maxOccurs="1" type="xs:anyType" />
|
|
||||||
</xs:all>
|
|
||||||
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
|
||||||
<xs:annotation>
|
|
||||||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
|
||||||
</xs:annotation>
|
|
||||||
</xs:attribute>
|
|
||||||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
|
||||||
<xs:annotation>
|
|
||||||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
|
||||||
</xs:annotation>
|
|
||||||
</xs:attribute>
|
|
||||||
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
|
||||||
<xs:annotation>
|
|
||||||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
|
||||||
</xs:annotation>
|
|
||||||
</xs:attribute>
|
|
||||||
</xs:complexType>
|
|
||||||
</xs:element>
|
|
||||||
</xs:schema>
|
|
@ -1,13 +0,0 @@
|
|||||||
@using Wabbajack.App.Blazor.Shared
|
|
||||||
|
|
||||||
<CascadingBlazoredModal>
|
|
||||||
<Router AppAssembly="@GetType().Assembly">
|
|
||||||
<Found Context="routeData">
|
|
||||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
|
||||||
</Found>
|
|
||||||
<NotFound>
|
|
||||||
<h1>Not found</h1>
|
|
||||||
<p>Sorry, there's nothing here.</p>
|
|
||||||
</NotFound>
|
|
||||||
</Router>
|
|
||||||
</CascadingBlazoredModal>
|
|
@ -1,29 +0,0 @@
|
|||||||
<mah:MetroWindow 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"
|
|
||||||
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
ShowTitleBar="False"
|
|
||||||
Title="WABBAJACK" Height="750" Width="1200" MinHeight="750" MinWidth="1200">
|
|
||||||
<Grid Background="#121212" MouseDown="UIElement_OnMouseDown">
|
|
||||||
<TabControl x:Name="Tabs">
|
|
||||||
<TabItem>
|
|
||||||
<TabItem.Header>
|
|
||||||
<TextBlock FontSize="16" Margin="0, 0, 8, 0">WABBAJACK 3.0.0</TextBlock>
|
|
||||||
</TabItem.Header>
|
|
||||||
<blazor:BlazorWebView HostPage="wwwroot\index.html" x:Name="BlazorWebView">
|
|
||||||
<blazor:BlazorWebView.RootComponents>
|
|
||||||
<blazor:RootComponent Selector="#app" ComponentType="{x:Type local:Main}" />
|
|
||||||
</blazor:BlazorWebView.RootComponents>
|
|
||||||
</blazor:BlazorWebView>
|
|
||||||
</TabItem>
|
|
||||||
</TabControl>
|
|
||||||
</Grid>
|
|
||||||
<Window.TaskbarItemInfo>
|
|
||||||
<TaskbarItemInfo x:Name="TaskBarItem"/>
|
|
||||||
</Window.TaskbarItemInfo>
|
|
||||||
</mah:MetroWindow>
|
|
@ -1,59 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Reactive.Disposables;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using ReactiveUI;
|
|
||||||
using Wabbajack.App.Blazor.Browser;
|
|
||||||
using Wabbajack.App.Blazor.Messages;
|
|
||||||
using Wabbajack.App.Blazor.State;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor;
|
|
||||||
|
|
||||||
public partial class MainWindow : IDisposable
|
|
||||||
{
|
|
||||||
private Point _lastPosition;
|
|
||||||
private readonly CompositeDisposable _compositeDisposable;
|
|
||||||
|
|
||||||
public MainWindow(IServiceProvider serviceProvider, IStateContainer stateContainer)
|
|
||||||
{
|
|
||||||
_compositeDisposable = new CompositeDisposable();
|
|
||||||
|
|
||||||
stateContainer.TaskBarStateObservable.Subscribe(state =>
|
|
||||||
{
|
|
||||||
Dispatcher.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
TaskBarItem.Description = state.Description;
|
|
||||||
TaskBarItem.ProgressState = state.State;
|
|
||||||
TaskBarItem.ProgressValue = state.ProgressValue;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
MessageBus.Current.Listen<OpenBrowserTab>()
|
|
||||||
.Subscribe(OnOpenBrowserTab)
|
|
||||||
.DisposeWith(_compositeDisposable);
|
|
||||||
|
|
||||||
InitializeComponent();
|
|
||||||
BlazorWebView.Services = serviceProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
this.DragMove();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnOpenBrowserTab(OpenBrowserTab msg)
|
|
||||||
{
|
|
||||||
var tab = new BrowserTabView(msg.ViewModel);
|
|
||||||
Tabs.Items.Add(tab);
|
|
||||||
Tabs.SelectedItem = tab;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_compositeDisposable.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required so compiler doesn't complain about not finding the type. [MC3050]
|
|
||||||
public partial class Main { }
|
|
@ -1,13 +0,0 @@
|
|||||||
using Wabbajack.App.Blazor.Browser;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Messages;
|
|
||||||
|
|
||||||
public class OpenBrowserTab
|
|
||||||
{
|
|
||||||
public BrowserTabViewModel ViewModel { get; set; }
|
|
||||||
|
|
||||||
public OpenBrowserTab(BrowserTabViewModel viewModel)
|
|
||||||
{
|
|
||||||
ViewModel = viewModel;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
@page "/create"
|
|
||||||
@namespace Wabbajack.App.Blazor.Pages
|
|
||||||
|
|
||||||
<div id="content">
|
|
||||||
<div class="resources">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
public const string Route = "/create";
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
@page "/gallery"
|
|
||||||
|
|
||||||
@namespace Wabbajack.App.Blazor.Pages
|
|
||||||
|
|
||||||
<div id="content">
|
|
||||||
@if (_errorLoadingModlists)
|
|
||||||
{
|
|
||||||
@* TODO: error *@
|
|
||||||
}
|
|
||||||
else if (!Modlists.Any())
|
|
||||||
{
|
|
||||||
@* TODO: loading *@
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@foreach (var modlist in Modlists)
|
|
||||||
{
|
|
||||||
<div @key="modlist.Title" class="item">
|
|
||||||
<div class="display">
|
|
||||||
<img src="@modlist.Links.ImageUri" loading="lazy" class="image" alt="@modlist.Title">
|
|
||||||
<div class="interaction">
|
|
||||||
<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))"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="info">
|
|
||||||
<div class="title">@modlist.Title</div>
|
|
||||||
<div class="author">@modlist.Author</div>
|
|
||||||
<div class="description">@modlist.Description</div>
|
|
||||||
</div>
|
|
||||||
<div class="tags"></div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (DownloadingMetaData is not null)
|
|
||||||
{
|
|
||||||
<BottomBar Image="@DownloadingMetaData.Links.ImageUri" Title="Downloading..." Subtitle="@DownloadingMetaData.Title">
|
|
||||||
<div style="height:1.5rem;">
|
|
||||||
<ProgressBar ProgressObserver="@DownloadProgress"/>
|
|
||||||
</div>
|
|
||||||
</BottomBar>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
public const string Route = "/gallery";
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reactive.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Shell;
|
|
||||||
using Blazored.Modal;
|
|
||||||
using Blazored.Modal.Services;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Wabbajack.App.Blazor.Components;
|
|
||||||
using Wabbajack.App.Blazor.State;
|
|
||||||
using Wabbajack.DTOs;
|
|
||||||
using Wabbajack.RateLimiter;
|
|
||||||
using Wabbajack.Services.OSIntegrated.Services;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Pages;
|
|
||||||
|
|
||||||
public partial class Gallery
|
|
||||||
{
|
|
||||||
[Inject] private ILogger<Gallery> Logger { get; set; } = default!;
|
|
||||||
[Inject] private IStateContainer StateContainer { get; set; } = default!;
|
|
||||||
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
|
||||||
[Inject] private ModListDownloadMaintainer Maintainer { get; set; } = default!;
|
|
||||||
[Inject] private IModalService Modal { get; set; } = default!;
|
|
||||||
|
|
||||||
private IObservable<Percent>? DownloadProgress { get; set; }
|
|
||||||
private ModlistMetadata? DownloadingMetaData { get; set; }
|
|
||||||
|
|
||||||
private IEnumerable<ModlistMetadata> Modlists => StateContainer.Modlists;
|
|
||||||
|
|
||||||
private bool _errorLoadingModlists;
|
|
||||||
|
|
||||||
private bool _shouldRender;
|
|
||||||
protected override bool ShouldRender() => _shouldRender;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
if (!StateContainer.Modlists.Any())
|
|
||||||
{
|
|
||||||
var res = await StateContainer.LoadModlistMetadata();
|
|
||||||
if (!res)
|
|
||||||
{
|
|
||||||
_errorLoadingModlists = true;
|
|
||||||
_shouldRender = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_shouldRender = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task OnClickDownload(ModlistMetadata metadata)
|
|
||||||
{
|
|
||||||
if (!await Maintainer.HaveModList(metadata)) await Download(metadata);
|
|
||||||
StateContainer.ModlistPath = Maintainer.ModListPath(metadata);
|
|
||||||
StateContainer.Modlist = null;
|
|
||||||
NavigationManager.NavigateTo(Configure.Route);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnClickInformation(ModlistMetadata metadata)
|
|
||||||
{
|
|
||||||
// TODO: [High] Implement information modal.
|
|
||||||
var parameters = new ModalParameters();
|
|
||||||
parameters.Add(nameof(InfoModal.Content), metadata.Description);
|
|
||||||
Modal.Show<InfoModal>("Information", parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Download(ModlistMetadata metadata)
|
|
||||||
{
|
|
||||||
StateContainer.NavigationAllowed = false;
|
|
||||||
DownloadingMetaData = metadata;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var (progress, task) = Maintainer.DownloadModlist(metadata);
|
|
||||||
|
|
||||||
DownloadProgress = progress;
|
|
||||||
|
|
||||||
var dispose = progress
|
|
||||||
.Sample(TimeSpan.FromMilliseconds(250))
|
|
||||||
.DistinctUntilChanged(p => p.Value)
|
|
||||||
.Subscribe(p => {
|
|
||||||
StateContainer.TaskBarState = new TaskBarState
|
|
||||||
{
|
|
||||||
Description = $"Downloading {metadata.Title}",
|
|
||||||
State = TaskbarItemProgressState.Normal,
|
|
||||||
ProgressValue = p.Value
|
|
||||||
};
|
|
||||||
}, () => { StateContainer.TaskBarState = new TaskBarState(); });
|
|
||||||
|
|
||||||
await task;
|
|
||||||
dispose.Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError(e, "Exception downloading Modlist {Name}", metadata.Title);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
StateContainer.TaskBarState = new TaskBarState();
|
|
||||||
StateContainer.NavigationAllowed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
@import "../Shared/Globals.scss";
|
|
||||||
|
|
||||||
#content {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
}
|
|
||||||
|
|
||||||
$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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
@page "/install/configure"
|
|
||||||
|
|
||||||
@namespace Wabbajack.App.Blazor.Pages
|
|
||||||
|
|
||||||
<div id="content">
|
|
||||||
<div class="install-background">
|
|
||||||
@if (ModlistImage is not null)
|
|
||||||
{
|
|
||||||
<img id="background-image" src="@ModlistImage" alt=""/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="list">
|
|
||||||
@if (Modlist is not null)
|
|
||||||
{
|
|
||||||
<div class="left-side">
|
|
||||||
<InfoBlock Title="@Modlist.Name" Subtitle="@Modlist.Author" Comment="@Modlist.Version.ToString()" Description="@Modlist.Description"/>
|
|
||||||
</div>
|
|
||||||
<div class="right-side">
|
|
||||||
@if (ModlistImage is not null)
|
|
||||||
{
|
|
||||||
<InfoImage Image="@ModlistImage"/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="settings">
|
|
||||||
<div class="locations">
|
|
||||||
<div class="labels">
|
|
||||||
<span>Target Modlist</span>
|
|
||||||
<span>Install Location</span>
|
|
||||||
<span>Download Location</span>
|
|
||||||
</div>
|
|
||||||
<div class="paths">
|
|
||||||
<span class="modlist-file">@ModlistPath.ToString()</span>
|
|
||||||
<span class="install-location" @onclick="SelectInstallFolder">@InstallPath.ToString()</span>
|
|
||||||
<span class="download-location" @onclick="SelectDownloadFolder">@DownloadPath.ToString()</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="options">
|
|
||||||
<OptionCheckbox Label="Overwrite Installation" @bind-IsChecked="OverwriteInstallation"/>
|
|
||||||
<OptionCheckbox Label="NTFS Compression" @bind-IsChecked="UseCompression"/>
|
|
||||||
<OptionCheckbox Label="Do a sweet trick"/>
|
|
||||||
<OptionCheckbox Label="Something else"/>
|
|
||||||
</div>
|
|
||||||
<div class="install">
|
|
||||||
<img src="images/icons/play.svg" @onclick="Install" alt="Install">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
public const string Route = "/install/configure";
|
|
||||||
}
|
|
@ -1,132 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Wabbajack.DTOs;
|
|
||||||
using Wabbajack.DTOs.JsonConverters;
|
|
||||||
using Wabbajack.Installer;
|
|
||||||
using Wabbajack.Paths;
|
|
||||||
using Wabbajack.App.Blazor.Utility;
|
|
||||||
using Wabbajack.Hashing.xxHash64;
|
|
||||||
using Wabbajack.Services.OSIntegrated;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Blazored.Toast.Services;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.JSInterop;
|
|
||||||
using Wabbajack.App.Blazor.State;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Pages;
|
|
||||||
|
|
||||||
public partial class Configure
|
|
||||||
{
|
|
||||||
[Inject] private ILogger<Configure> Logger { get; set; } = default!;
|
|
||||||
[Inject] private IStateContainer StateContainer { get; set; } = default!;
|
|
||||||
[Inject] private DTOSerializer DTOs { get; set; } = default!;
|
|
||||||
[Inject] private SettingsManager SettingsManager { get; set; } = default!;
|
|
||||||
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
|
||||||
[Inject] private IJSRuntime JSRuntime { get; set; } = default!;
|
|
||||||
[Inject] private IToastService ToastService { get; set; } = default!;
|
|
||||||
|
|
||||||
private ModList? Modlist => StateContainer.Modlist;
|
|
||||||
private string? ModlistImage => StateContainer.ModlistImage;
|
|
||||||
private AbsolutePath ModlistPath => StateContainer.ModlistPath;
|
|
||||||
private AbsolutePath InstallPath => StateContainer.InstallPath;
|
|
||||||
private AbsolutePath DownloadPath => StateContainer.DownloadPath;
|
|
||||||
|
|
||||||
private const string InstallSettingsPrefix = "install-settings-";
|
|
||||||
|
|
||||||
private bool OverwriteInstallation { get; set; }
|
|
||||||
private bool UseCompression { get; set; }
|
|
||||||
|
|
||||||
private bool _shouldRender;
|
|
||||||
protected override bool ShouldRender() => _shouldRender;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
await LoadModlist();
|
|
||||||
_shouldRender = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadModlist()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (ModlistPath == AbsolutePath.Empty) throw new FileNotFoundException("Modlist path was empty.");
|
|
||||||
var modlist = await StandardInstaller.LoadFromFile(DTOs, ModlistPath);
|
|
||||||
StateContainer.Modlist = modlist;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
ToastService.ShowError("Could not load modlist!");
|
|
||||||
Logger.LogError(e, "Exception loading Modlist file {Name}", ModlistPath.ToString());
|
|
||||||
NavigationManager.NavigateTo(Select.Route);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var hex = (await ModlistPath.ToString().Hash()).ToHex();
|
|
||||||
var prevSettings = await SettingsManager.Load<SavedInstallSettings>(InstallSettingsPrefix + hex);
|
|
||||||
if (prevSettings.ModlistLocation == ModlistPath)
|
|
||||||
{
|
|
||||||
StateContainer.ModlistPath = prevSettings.ModlistLocation;
|
|
||||||
StateContainer.InstallPath = prevSettings.InstallLocation;
|
|
||||||
StateContainer.DownloadPath = prevSettings.DownloadLocation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogWarning(e, "Exception loading previous settings for {Name}", ModlistPath.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var imageStream = await StandardInstaller.ModListImageStream(ModlistPath);
|
|
||||||
var dotnetImageStream = new DotNetStreamReference(imageStream);
|
|
||||||
StateContainer.ModlistImage = await JSRuntime.InvokeAsync<string>(JsInterop.GetBlobUrlFromStream, dotnetImageStream);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
ToastService.ShowWarning("Could not load modlist image.");
|
|
||||||
Logger.LogWarning(e, "Exception loading modlist image for {Name}", ModlistPath.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SelectInstallFolder()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var installPath = await Dialog.ShowDialogNonBlocking(true);
|
|
||||||
if (installPath is not null) StateContainer.InstallPath = (AbsolutePath) installPath;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError(e, "Exception selecting install folder");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SelectDownloadFolder()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var downloadPath = await Dialog.ShowDialogNonBlocking(true);
|
|
||||||
if (downloadPath is not null) StateContainer.DownloadPath = (AbsolutePath) downloadPath;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError(e, "Exception selecting download folder");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Install()
|
|
||||||
{
|
|
||||||
NavigationManager.NavigateTo(Installing.Route);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class SavedInstallSettings
|
|
||||||
{
|
|
||||||
public AbsolutePath ModlistLocation { get; set; } = AbsolutePath.Empty;
|
|
||||||
public AbsolutePath InstallLocation { get; set; } = AbsolutePath.Empty;
|
|
||||||
public AbsolutePath DownloadLocation { get; set; } = AbsolutePath.Empty;
|
|
||||||
// public ModlistMetadata Metadata { get; set; }
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
@page "/install/installing"
|
|
||||||
|
|
||||||
@namespace Wabbajack.App.Blazor.Pages
|
|
||||||
|
|
||||||
<div id="content">
|
|
||||||
<div class="install-background">
|
|
||||||
@if (ModlistImage is not null)
|
|
||||||
{
|
|
||||||
<img id="background-image" src="@ModlistImage" alt=""/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="list">
|
|
||||||
@if (Modlist is not null)
|
|
||||||
{
|
|
||||||
<div class="left-side">
|
|
||||||
<InfoBlock Supertitle="@StatusCategory" Title="@Modlist.Name"/>
|
|
||||||
<div class="step-logger">
|
|
||||||
@foreach (var step in StatusStep.Take(3))
|
|
||||||
{
|
|
||||||
<div class="step">@step</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="right-side">
|
|
||||||
<InfoImage Image="@ModlistImage" Title="Some Mod Title" Subtitle="Author and others" Description="This mod adds something cool but I'm not going to tell you anything."/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="logger-container">
|
|
||||||
<VirtualLogger/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
public const string Route = "/install/installing";
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
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 Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.JSInterop;
|
|
||||||
using Wabbajack.App.Blazor.State;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Pages;
|
|
||||||
|
|
||||||
public partial class Installing
|
|
||||||
{
|
|
||||||
[Inject] private ILogger<Configure> Logger { get; set; } = default!;
|
|
||||||
[Inject] private IStateContainer StateContainer { get; set; } = default!;
|
|
||||||
[Inject] private DTOSerializer DTOs { get; set; } = default!;
|
|
||||||
[Inject] private IServiceProvider ServiceProvider { get; set; } = default!;
|
|
||||||
[Inject] private SystemParametersConstructor ParametersConstructor { get; set; } = default!;
|
|
||||||
[Inject] private IGameLocator GameLocator { get; set; } = default!;
|
|
||||||
[Inject] private SettingsManager SettingsManager { get; set; } = default!;
|
|
||||||
[Inject] private IJSRuntime JSRuntime { get; set; } = default!;
|
|
||||||
|
|
||||||
private ModList? Modlist => StateContainer.Modlist;
|
|
||||||
private string? ModlistImage => StateContainer.ModlistImage;
|
|
||||||
private AbsolutePath ModlistPath => StateContainer.ModlistPath;
|
|
||||||
private AbsolutePath InstallPath => StateContainer.InstallPath;
|
|
||||||
private AbsolutePath DownloadPath => StateContainer.DownloadPath;
|
|
||||||
|
|
||||||
private string? StatusCategory { get; set; }
|
|
||||||
private string? LastStatus { get; set; }
|
|
||||||
private List<string> StatusStep { get; set; } = new();
|
|
||||||
|
|
||||||
private const string InstallSettingsPrefix = "install-settings-";
|
|
||||||
|
|
||||||
private bool _shouldRender;
|
|
||||||
protected override bool ShouldRender() => _shouldRender;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
_shouldRender = true;
|
|
||||||
await Task.Run(Install);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Install()
|
|
||||||
{
|
|
||||||
if (Modlist is null) return;
|
|
||||||
StateContainer.InstallState = InstallState.Installing;
|
|
||||||
await BeginInstall(Modlist);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task BeginInstall(ModList modlist)
|
|
||||||
{
|
|
||||||
var postfix = (await ModlistPath.ToString().Hash()).ToHex();
|
|
||||||
await SettingsManager.Save(InstallSettingsPrefix + postfix, new SavedInstallSettings
|
|
||||||
{
|
|
||||||
ModlistLocation = ModlistPath,
|
|
||||||
InstallLocation = InstallPath,
|
|
||||||
DownloadLocation = 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 (LastStatus == update.StatusText) return;
|
|
||||||
StatusStep.Insert(0, update.StatusText);
|
|
||||||
StatusCategory = update.StatusCategory;
|
|
||||||
LastStatus = update.StatusText;
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
};
|
|
||||||
|
|
||||||
await installer.Begin(CancellationToken.None);
|
|
||||||
StateContainer.InstallState = InstallState.Success;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError(e, "Exception installing Modlist");
|
|
||||||
StateContainer.InstallState = InstallState.Failure;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,149 +0,0 @@
|
|||||||
@import "../../Shared/Globals.scss";
|
|
||||||
|
|
||||||
$checkbox-background: rgba(255, 255, 255, 0.2);
|
|
||||||
$checkbox-background-hover: darkgrey;
|
|
||||||
$checkbox-background-checked: $accent-color;
|
|
||||||
$checkbox-size: 0.75rem;
|
|
||||||
|
|
||||||
@mixin path-span {
|
|
||||||
display: block;
|
|
||||||
height: 2rem;
|
|
||||||
padding: 0.25rem;
|
|
||||||
margin: 0.25rem;
|
|
||||||
white-space: pre;
|
|
||||||
cursor: pointer;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
align-content: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
align-items: center;
|
|
||||||
color: white;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.install-background {
|
|
||||||
position: absolute;
|
|
||||||
width: calc(100% - #{$sidebar-width});
|
|
||||||
height: calc(100% - #{$header-height});
|
|
||||||
filter: blur(25px) brightness(50%);
|
|
||||||
z-index: -1;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.list {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.left-side, .right-side {
|
|
||||||
flex: 1;
|
|
||||||
margin: 1rem;
|
|
||||||
|
|
||||||
.step-logger {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-content: center;
|
|
||||||
|
|
||||||
.step {
|
|
||||||
&:nth-child(1) {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 100;
|
|
||||||
color: rgba(255, 255, 255, 0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(2) {
|
|
||||||
margin-left: 0.75rem;
|
|
||||||
font-size: 1.85rem;
|
|
||||||
font-weight: 100;
|
|
||||||
color: rgba(255, 255, 255, 0.6);
|
|
||||||
filter: blur(1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(3) {
|
|
||||||
margin-left: 1rem;
|
|
||||||
font-size: 1.7rem;
|
|
||||||
font-weight: 100;
|
|
||||||
color: rgba(255, 255, 255, 0.4);
|
|
||||||
filter: blur(1.5px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logger-container {
|
|
||||||
height: 200px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.5rem;
|
|
||||||
background: rgba(0, 0, 0, 0.2);
|
|
||||||
color: lightgrey;
|
|
||||||
border: solid 1px black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
padding: 1rem;
|
|
||||||
backdrop-filter: brightness(0.5);
|
|
||||||
|
|
||||||
.locations {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.labels {
|
|
||||||
span {
|
|
||||||
@include path-span;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.paths {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: 1rem;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
span {
|
|
||||||
@include path-span;
|
|
||||||
border: solid 1px rgba(255, 255, 255, 0.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.options {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-left: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.install {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
margin: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 5rem;
|
|
||||||
height: 5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
@page "/install/select"
|
|
||||||
|
|
||||||
@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>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
public const string Route = "/install/select";
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
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 Select
|
|
||||||
{
|
|
||||||
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
|
||||||
[Inject] private IStateContainer StateContainer { get; set; } = default!;
|
|
||||||
|
|
||||||
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;
|
|
||||||
StateContainer.ModlistPath = dialog.FileName.ToAbsolutePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
NavigationManager.NavigateTo(Configure.Route);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
$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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
@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());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
|
||||||
public const string Route = "/";
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
#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;
|
|
||||||
//}
|
|
@ -1,40 +0,0 @@
|
|||||||
@page "/settings"
|
|
||||||
@using ReactiveUI
|
|
||||||
@using Wabbajack.App.Blazor.Browser.ViewModels
|
|
||||||
@using Wabbajack.App.Blazor.Messages
|
|
||||||
@using Microsoft.Extensions.DependencyInjection
|
|
||||||
@namespace Wabbajack.App.Blazor.Pages
|
|
||||||
|
|
||||||
@inject IServiceProvider _serviceProvider;
|
|
||||||
|
|
||||||
<div id="content">
|
|
||||||
<div class="resources">
|
|
||||||
|
|
||||||
<button onclick="@LoginToNexus">Login To Nexus</button>
|
|
||||||
<button onclick="@LoginToVectorPlexus">Login To Vector Plexus</button>
|
|
||||||
<button onclick="@LoginToLoversLab">Login To Lovers Lab</button>
|
|
||||||
<button onclick="@LoginToBethesdaNet">Login To Bethesda Net</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
public const string Route = "/settings";
|
|
||||||
|
|
||||||
public void LoginToNexus()
|
|
||||||
{
|
|
||||||
MessageBus.Current.SendMessage(new OpenBrowserTab(_serviceProvider.GetRequiredService<NexusLogin>()));
|
|
||||||
}
|
|
||||||
public void LoginToLoversLab()
|
|
||||||
{
|
|
||||||
MessageBus.Current.SendMessage(new OpenBrowserTab(_serviceProvider.GetRequiredService<LoversLab>()));
|
|
||||||
}
|
|
||||||
public void LoginToVectorPlexus()
|
|
||||||
{
|
|
||||||
MessageBus.Current.SendMessage(new OpenBrowserTab(_serviceProvider.GetRequiredService<VectorPlexus>()));
|
|
||||||
}
|
|
||||||
public void LoginToBethesdaNet()
|
|
||||||
{
|
|
||||||
MessageBus.Current.SendMessage(new OpenBrowserTab(_serviceProvider.GetRequiredService<BethesdaNetLogin>()));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
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
|
|
||||||
{
|
|
||||||
var resource = await _resourceSettingsManager.GetSettings("Downloads");
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex) { }
|
|
||||||
|
|
||||||
await base.OnInitializedAsync();
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 139 KiB |
@ -1,21 +0,0 @@
|
|||||||
@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;
|
|
@ -1,14 +0,0 @@
|
|||||||
@using Blazored.Toast.Configuration
|
|
||||||
@inherits LayoutComponentBase
|
|
||||||
|
|
||||||
@namespace Wabbajack.App.Blazor.Shared
|
|
||||||
|
|
||||||
<BlazoredToasts Position="ToastPosition.BottomRight"/>
|
|
||||||
<div id="background"></div>
|
|
||||||
<SideBar/>
|
|
||||||
<div id="wrapper">
|
|
||||||
<TopBar/>
|
|
||||||
<div id="page">
|
|
||||||
@Body
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,20 +0,0 @@
|
|||||||
@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});
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Wabbajack.DTOs;
|
|
||||||
using Wabbajack.Paths;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.State;
|
|
||||||
|
|
||||||
public interface IStateContainer
|
|
||||||
{
|
|
||||||
IEnumerable<ModlistMetadata> Modlists { get; }
|
|
||||||
Task<bool> LoadModlistMetadata();
|
|
||||||
|
|
||||||
IObservable<bool> NavigationAllowedObservable { get; }
|
|
||||||
bool NavigationAllowed { get; set; }
|
|
||||||
|
|
||||||
IObservable<AbsolutePath> ModlistPathObservable { get; }
|
|
||||||
AbsolutePath ModlistPath { get; set; }
|
|
||||||
|
|
||||||
IObservable<AbsolutePath> InstallPathObservable { get; }
|
|
||||||
AbsolutePath InstallPath { get; set; }
|
|
||||||
|
|
||||||
IObservable<AbsolutePath> DownloadPathObservable { get; }
|
|
||||||
AbsolutePath DownloadPath { get; set; }
|
|
||||||
|
|
||||||
IObservable<ModList?> ModlistObservable { get; }
|
|
||||||
ModList? Modlist { get; set; }
|
|
||||||
|
|
||||||
IObservable<string?> ModlistImageObservable { get; }
|
|
||||||
string? ModlistImage { get; set; }
|
|
||||||
|
|
||||||
IObservable<InstallState> InstallStateObservable { get; }
|
|
||||||
InstallState InstallState { get; set; }
|
|
||||||
|
|
||||||
IObservable<TaskBarState> TaskBarStateObservable { get; }
|
|
||||||
TaskBarState TaskBarState { get; set; }
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
namespace Wabbajack.App.Blazor.State;
|
|
||||||
|
|
||||||
public enum InstallState
|
|
||||||
{
|
|
||||||
Waiting,
|
|
||||||
Configuration,
|
|
||||||
Installing,
|
|
||||||
Success,
|
|
||||||
Failure
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
using Wabbajack.DTOs;
|
|
||||||
using Wabbajack.Networking.WabbajackClientApi;
|
|
||||||
using Wabbajack.Paths;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.State;
|
|
||||||
|
|
||||||
public class StateContainer : IStateContainer
|
|
||||||
{
|
|
||||||
private readonly ILogger<StateContainer> _logger;
|
|
||||||
private readonly Client _client;
|
|
||||||
|
|
||||||
public StateContainer(ILogger<StateContainer> logger, Client client)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ModlistMetadata[] _modlists = Array.Empty<ModlistMetadata>();
|
|
||||||
public IEnumerable<ModlistMetadata> Modlists => _modlists;
|
|
||||||
|
|
||||||
public async Task<bool> LoadModlistMetadata()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var lists = await _client.LoadLists();
|
|
||||||
_modlists = lists;
|
|
||||||
return _modlists.Any();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.LogError(e, "Exception loading Modlists");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly CustomObservable<bool> _navigationAllowedObservable = new(true);
|
|
||||||
public IObservable<bool> NavigationAllowedObservable => _navigationAllowedObservable;
|
|
||||||
public bool NavigationAllowed
|
|
||||||
{
|
|
||||||
get => _navigationAllowedObservable.Value;
|
|
||||||
set => _navigationAllowedObservable.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly CustomObservable<AbsolutePath> _modlistPathObservable = new(AbsolutePath.Empty);
|
|
||||||
public IObservable<AbsolutePath> ModlistPathObservable => _modlistPathObservable;
|
|
||||||
public AbsolutePath ModlistPath
|
|
||||||
{
|
|
||||||
get => _modlistPathObservable.Value;
|
|
||||||
set => _modlistPathObservable.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly CustomObservable<AbsolutePath> _installPathObservable = new(AbsolutePath.Empty);
|
|
||||||
public IObservable<AbsolutePath> InstallPathObservable => _installPathObservable;
|
|
||||||
public AbsolutePath InstallPath
|
|
||||||
{
|
|
||||||
get => _installPathObservable.Value;
|
|
||||||
set => _installPathObservable.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly CustomObservable<AbsolutePath> _downloadPathObservable = new(AbsolutePath.Empty);
|
|
||||||
public IObservable<AbsolutePath> DownloadPathObservable => _downloadPathObservable;
|
|
||||||
public AbsolutePath DownloadPath
|
|
||||||
{
|
|
||||||
get => _downloadPathObservable.Value;
|
|
||||||
set => _downloadPathObservable.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly CustomObservable<ModList?> _modlistObservable = new(null);
|
|
||||||
public IObservable<ModList?> ModlistObservable => _modlistObservable;
|
|
||||||
public ModList? Modlist
|
|
||||||
{
|
|
||||||
get => _modlistObservable.Value;
|
|
||||||
set => _modlistObservable.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly CustomObservable<string?> _modlistImageObservable = new(string.Empty);
|
|
||||||
public IObservable<string?> ModlistImageObservable => _modlistImageObservable;
|
|
||||||
public string? ModlistImage
|
|
||||||
{
|
|
||||||
get => _modlistImageObservable.Value;
|
|
||||||
set => _modlistImageObservable.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly CustomObservable<InstallState> _installStateObservable = new(InstallState.Waiting);
|
|
||||||
public IObservable<InstallState> InstallStateObservable => _installStateObservable;
|
|
||||||
public InstallState InstallState
|
|
||||||
{
|
|
||||||
get => _installStateObservable.Value;
|
|
||||||
set => _installStateObservable.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly CustomObservable<TaskBarState> _taskBarStateObservable = new(new TaskBarState());
|
|
||||||
public IObservable<TaskBarState> TaskBarStateObservable => _taskBarStateObservable;
|
|
||||||
public TaskBarState TaskBarState
|
|
||||||
{
|
|
||||||
get => _taskBarStateObservable.Value;
|
|
||||||
set => _taskBarStateObservable.Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
using System.Windows.Shell;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.State;
|
|
||||||
|
|
||||||
public class TaskBarState
|
|
||||||
{
|
|
||||||
public string Description { get; set; } = string.Empty;
|
|
||||||
public double ProgressValue { get; set; }
|
|
||||||
public TaskbarItemProgressState State { get; set; } = TaskbarItemProgressState.None;
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
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;
|
|
||||||
var result = dialog.ShowDialog(newWindow);
|
|
||||||
return result == CommonFileDialogResult.Ok ? dialog.FileName : null;
|
|
||||||
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext())
|
|
||||||
.ContinueWith(result => result.Result?.ToAbsolutePath())
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
using Microsoft.JSInterop;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Utility;
|
|
||||||
|
|
||||||
public static class JsInterop
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a <see cref="DotNetStreamReference"/> into a blob URL. Useful for streaming images.
|
|
||||||
/// <code>async function getBlobUrlFromStream(imageStream: DotNetStreamReference)</code>
|
|
||||||
/// </summary>
|
|
||||||
public const string GetBlobUrlFromStream = "getBlobUrlFromStream";
|
|
||||||
}
|
|
@ -1,171 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using PInvoke;
|
|
||||||
using Silk.NET.Core.Native;
|
|
||||||
using Silk.NET.DXGI;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
using Wabbajack.Installer;
|
|
||||||
using Wabbajack.Paths.IO;
|
|
||||||
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;
|
|
||||||
|
|
||||||
_logger.LogInformation("Wabbajack Build - {Sha}", ThisAssembly.Git.Sha);
|
|
||||||
_logger.LogInformation("Running in {EntryPoint}", KnownFolders.EntryPoint);
|
|
||||||
|
|
||||||
_logger.LogInformation("Detected Windows Version: {Version}", Environment.OSVersion.VersionString);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<(int Width, int Height, bool IsPrimary)> GetDisplays()
|
|
||||||
{
|
|
||||||
// Needed to make sure we get the right values from this call
|
|
||||||
SetProcessDPIAware();
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
var col = new List<(int Width, int Height, bool IsPrimary)>();
|
|
||||||
|
|
||||||
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
|
|
||||||
delegate(IntPtr hMonitor, IntPtr hdcMonitor, RECT* lprcMonitor, void* dwData)
|
|
||||||
{
|
|
||||||
var mi = new MONITORINFOEX();
|
|
||||||
mi.cbSize = Marshal.SizeOf(mi);
|
|
||||||
var success = GetMonitorInfo(hMonitor, (MONITORINFO*)&mi);
|
|
||||||
if (success)
|
|
||||||
col.Add((mi.Monitor.right - mi.Monitor.left, mi.Monitor.bottom - mi.Monitor.top,
|
|
||||||
mi.Flags == MONITORINFO_Flags.MONITORINFOF_PRIMARY));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}, IntPtr.Zero);
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SystemParameters Create()
|
|
||||||
{
|
|
||||||
(var width, var height, _) = GetDisplays().First(d => d.IsPrimary);
|
|
||||||
|
|
||||||
/*using var f = new SharpDX.DXGI.Factory1();
|
|
||||||
var video_memory = f.Adapters1.Select(a =>
|
|
||||||
Math.Max(a.Description.DedicatedSystemMemory, (long)a.Description.DedicatedVideoMemory)).Max();*/
|
|
||||||
|
|
||||||
var dxgiMemory = 0UL;
|
|
||||||
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
using var 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));
|
|
||||||
|
|
||||||
var i = 0u;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
IDXGIAdapter1* adapter1 = default;
|
|
||||||
|
|
||||||
//https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgifactory1-enumadapters1
|
|
||||||
var res = factory1->EnumAdapters1(i, &adapter1);
|
|
||||||
|
|
||||||
var 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));
|
|
||||||
|
|
||||||
var systemMemory = (ulong)adapterDesc.DedicatedSystemMemory;
|
|
||||||
var videoMemory = (ulong)adapterDesc.DedicatedVideoMemory;
|
|
||||||
|
|
||||||
var 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var memory = GetMemoryStatus();
|
|
||||||
var p = new SystemParameters
|
|
||||||
{
|
|
||||||
ScreenWidth = width,
|
|
||||||
ScreenHeight = height,
|
|
||||||
VideoMemorySize = (long)dxgiMemory,
|
|
||||||
SystemMemorySize = (long)memory.ullTotalPhys,
|
|
||||||
SystemPageSize = (long)memory.ullTotalPageFile - (long)memory.ullTotalPhys
|
|
||||||
};
|
|
||||||
|
|
||||||
_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");
|
|
||||||
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
[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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Reactive.Subjects;
|
|
||||||
using NLog;
|
|
||||||
using NLog.Targets;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Utility;
|
|
||||||
|
|
||||||
public class UiLoggerTarget : TargetWithLayout
|
|
||||||
{
|
|
||||||
private readonly Subject<string> _logs = new();
|
|
||||||
public IObservable<string> Logs => _logs;
|
|
||||||
|
|
||||||
protected override void Write(LogEventInfo logEvent)
|
|
||||||
{
|
|
||||||
_logs.OnNext(RenderLogEvent(Layout, logEvent));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using Wabbajack.Paths;
|
|
||||||
using Wabbajack.Paths.IO;
|
|
||||||
|
|
||||||
namespace Wabbajack.App.Blazor.Utility
|
|
||||||
{
|
|
||||||
public static class UIUtils
|
|
||||||
{
|
|
||||||
public static void OpenWebsite(Uri url)
|
|
||||||
{
|
|
||||||
Process.Start(new ProcessStartInfo("cmd.exe", $"/c start {url}")
|
|
||||||
{
|
|
||||||
CreateNoWindow = true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void OpenFolder(AbsolutePath path)
|
|
||||||
{
|
|
||||||
Process.Start(new ProcessStartInfo(KnownFolders.Windows.Combine("explorer.exe").ToString(), path.ToString())
|
|
||||||
{
|
|
||||||
CreateNoWindow = true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static AbsolutePath OpenFileDialog(string filter, string? initialDirectory = null)
|
|
||||||
{
|
|
||||||
var ofd = new OpenFileDialog();
|
|
||||||
ofd.Filter = filter;
|
|
||||||
ofd.InitialDirectory = initialDirectory ?? string.Empty;
|
|
||||||
if (ofd.ShowDialog() == DialogResult.OK)
|
|
||||||
return (AbsolutePath)ofd.FileName;
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
<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="Blazored.Modal" Version="7.0.0" />
|
|
||||||
<PackageReference Include="Blazored.Toast" Version="3.2.2" />
|
|
||||||
<PackageReference Include="DynamicData" Version="7.10.2" />
|
|
||||||
<PackageReference Include="Fizzler.Systems.HtmlAgilityPack" Version="1.2.1" />
|
|
||||||
<PackageReference Include="GitInfo" Version="2.2.0" />
|
|
||||||
<PackageReference Include="MahApps.Metro" Version="2.4.9" />
|
|
||||||
<PackageReference Include="MahApps.Metro.IconPacks" Version="4.11.0" />
|
|
||||||
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.4" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Wpf" Version="6.0.540" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.9" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.9" />
|
|
||||||
<PackageReference Include="NLog" Version="5.0.4" />
|
|
||||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.0.4" />
|
|
||||||
<PackageReference Include="PInvoke.User32" Version="0.7.124" />
|
|
||||||
<PackageReference Include="ReactiveUI" Version="18.3.1" />
|
|
||||||
<PackageReference Include="ReactiveUI.Fody" Version="18.3.1" />
|
|
||||||
<PackageReference Include="ReactiveUI.WPF" Version="18.3.1" />
|
|
||||||
<PackageReference Include="Silk.NET.DXGI" Version="2.16.0" />
|
|
||||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<ApplicationIcon>Resources\Icons\wabbajack.ico</ApplicationIcon>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove="Browser\BrowserWindow.xaml.cs" />
|
|
||||||
</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>
|
|
@ -1,12 +0,0 @@
|
|||||||
@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
|
|
||||||
@using Blazored.Modal
|
|
||||||
@using Blazored.Modal.Services
|
|
||||||
@using Blazored.Toast
|
|
||||||
@using Blazored.Toast.Services
|
|
@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 187 KiB |
Before Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 182 KiB |
Before Width: | Height: | Size: 272 KiB |
Before Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 58 KiB |
@ -1,9 +0,0 @@
|
|||||||
<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>
|
|
@ -1,7 +0,0 @@
|
|||||||
<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>
|
|
@ -1,7 +0,0 @@
|
|||||||
<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>
|
|