Remove Avalonia code, move WPF code around
@ -209,7 +209,7 @@
|
||||
ViewModel="{Binding}" />
|
||||
<local:AttentionBorder x:Name="UserInterventionsControl" Grid.Column="2">
|
||||
<Grid>
|
||||
<local:ConfirmationInterventionView DataContext="{Binding ActiveGlobalUserIntervention}" Visibility="{Binding ActiveGlobalUserIntervention, Converter={StaticResource IsTypeVisibilityConverter}, ConverterParameter={x:Type lib:ConfirmationIntervention}}" />
|
||||
<local:ConfirmationInterventionView DataContext="{Binding ActiveGlobalUserIntervention}" Visibility="{Binding ActiveGlobalUserIntervention, Converter={StaticResource IsTypeVisibilityConverter}, ConverterParameter={x:Type local:ConfirmationIntervention}}" />
|
||||
</Grid>
|
||||
</local:AttentionBorder>
|
||||
<local:CompilationCompleteView Grid.Column="2"
|
||||
|
@ -1,123 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Platforms>x64</Platforms>
|
||||
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
|
||||
<AssemblyVersion>2.5.3.4</AssemblyVersion>
|
||||
<FileVersion>2.5.3.4</FileVersion>
|
||||
<Copyright>Copyright © 2019-2021</Copyright>
|
||||
<Description>An automated ModList installer</Description>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<StartupObject></StartupObject>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<IncludeSymbolsInSingleFile>true</IncludeSymbolsInSingleFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>Resources\Icons\wabbajack.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- TODO: These updates are currently required because CefSharp.Wpf specifies
|
||||
<Private>false</Private>, which means these libraries will not be specified in
|
||||
the .deps.json file, and so the CoreCLR wouldn't load these. -->
|
||||
<Reference Update="CefSharp">
|
||||
<Private>true</Private>
|
||||
</Reference>
|
||||
<Reference Update="CefSharp.Core">
|
||||
<Private>true</Private>
|
||||
</Reference>
|
||||
<Reference Update="CefSharp.Wpf">
|
||||
<Private>true</Private>
|
||||
</Reference>
|
||||
<Reference Include="MahApps.Metro.IconPacks.Octicons, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0c0d510f9915137a">
|
||||
<HintPath>..\..\..\Users\tbald\.nuget\packages\mahapps.metro.iconpacks\4.8.0\lib\netcoreapp3.1\MahApps.Metro.IconPacks.Octicons.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Readme.md" />
|
||||
<None Remove="Resources\GameGridIcons\Fallout4.png" />
|
||||
<None Remove="Resources\GameGridIcons\SkyrimSpecialEdition.png" />
|
||||
<None Remove="Resources\Icons\middle_mouse_button.png" />
|
||||
<None Remove="Resources\MO2Button.png" />
|
||||
<None Remove="Resources\VortexButton.png" />
|
||||
<None Remove="Resources\Wabba_Ded.png" />
|
||||
<None Remove="Resources\Wabba_Mouth.png" />
|
||||
<None Remove="Resources\Wabba_Mouth_No_Text.png" />
|
||||
<None Remove="Resources\Wabba_Mouth_Small.png" />
|
||||
<Compile Remove="View Models\Compilers\VortexCompilerVM.cs" />
|
||||
<Compile Remove="View Models\Installers\VortexInstallerVM.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="cef.redist.x64" Version="91.1.23" />
|
||||
<PackageReference Include="CefSharp.Common" Version="91.1.230" />
|
||||
<PackageReference Include="CefSharp.Wpf" Version="91.1.230">
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PackageReference>
|
||||
<PackageReference Include="DynamicData" Version="7.3.1" />
|
||||
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.1.0">
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Fody" Version="6.5.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Genbox.AlphaFS" Version="2.2.2.1" />
|
||||
<PackageReference Include="GitInfo" Version="2.1.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MahApps.Metro" Version="2.4.7" />
|
||||
<PackageReference Include="MahApps.Metro.IconPacks" Version="4.8.0" />
|
||||
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
|
||||
<PackageReference Include="PInvoke.User32" Version="0.7.104" />
|
||||
<PackageReference Include="ReactiveUI" Version="16.2.6" />
|
||||
<PackageReference Include="ReactiveUI.Fody" Version="16.2.6" />
|
||||
<PackageReference Include="ReactiveUI.WPF" Version="16.2.6" />
|
||||
<PackageReference Include="Silk.NET.DXGI" Version="2.6.0" />
|
||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||
<PackageReference Include="WPFThemes.DarkBlend" Version="1.0.8" />
|
||||
<PackageReference Include="CefSharp.OffScreen">
|
||||
<Version>91.1.230</Version>
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wabbajack.Services.OSIntegrated\Wabbajack.Services.OSIntegrated.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\middle_mouse_button.png" />
|
||||
<Resource Include="Resources\MO2Button.png" />
|
||||
<Resource Include="Resources\VortexButton.png" />
|
||||
<Resource Include="Resources\Wabba_Ded.png" />
|
||||
<Resource Include="Resources\Wabba_Mouth.png" />
|
||||
<Resource Include="Resources\Wabba_Mouth_No_Text.png" />
|
||||
<None Remove="LoginManagers\Icons\lovers_lab.png" />
|
||||
<EmbeddedResource Include="LoginManagers\Icons\lovers_lab.png" />
|
||||
<None Remove="LoginManagers\Icons\vector_plexus.png" />
|
||||
<EmbeddedResource Include="LoginManagers\Icons\vector_plexus.png" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<SplashScreen Include="Resources\Wabba_Mouth_Small.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="LoginManagers\Icons\nexus.png" />
|
||||
<EmbeddedResource Include="LoginManagers\Icons\nexus.png" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
10
Wabbajack.App/.gitignore
vendored
@ -1,10 +0,0 @@
|
||||
.idea/
|
||||
.vscode/
|
||||
.vs/
|
||||
|
||||
bin/
|
||||
obj/
|
||||
|
||||
*.user
|
||||
|
||||
./Library/
|
@ -1,16 +0,0 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Wabbajack.App"
|
||||
x:Class="Wabbajack.App.App">
|
||||
<Application.DataTemplates>
|
||||
<local:ViewLocator />
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Styles>
|
||||
<StyleInclude Source="avares://Material.Icons.Avalonia/App.xaml" />
|
||||
<FluentTheme Mode="Dark" />
|
||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||
<StyleInclude Source="avares://Wabbajack.App/Assets/Wabbajack.axaml" />
|
||||
|
||||
</Application.Styles>
|
||||
</Application>
|
@ -1,71 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Threading;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
using Wabbajack.App.Converters;
|
||||
using Wabbajack.App.Utilities;
|
||||
using Wabbajack.App.Views;
|
||||
|
||||
namespace Wabbajack.App;
|
||||
|
||||
public class App : Application
|
||||
{
|
||||
public static IServiceProvider Services { get; private set; } = null!;
|
||||
public static Window? MainWindow { get; set; }
|
||||
|
||||
public static event EventHandler FrameworkInitialized;
|
||||
public static event EventHandler FrameworkShutdown;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => Thread.CurrentThread.Name = "UIThread");
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
var host = Host.CreateDefaultBuilder(Array.Empty<string>())
|
||||
.ConfigureLogging(c => { c.ClearProviders(); })
|
||||
.ConfigureServices((host, services) => { services.AddAppServices(); }).Build();
|
||||
Services = host.Services;
|
||||
|
||||
SetupConverters();
|
||||
|
||||
// Need to startup the message bus;
|
||||
Services.GetService<MessageBus>();
|
||||
var app = Services.GetService<CefAppImpl>();
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow();
|
||||
desktop.Startup += Startup;
|
||||
desktop.Exit += Exit;
|
||||
MainWindow = desktop.MainWindow;
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
||||
private void Startup(object sender, ControlledApplicationLifetimeStartupEventArgs e)
|
||||
{
|
||||
FrameworkInitialized?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void Exit(object sender, ControlledApplicationLifetimeExitEventArgs e)
|
||||
{
|
||||
FrameworkShutdown?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void SetupConverters()
|
||||
{
|
||||
Locator.CurrentMutable.RegisterConstant<IBindingTypeConverter>(new AbsoultePathBindingConverter());
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 272 KiB |
@ -1,106 +0,0 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Wabbajack.App.Controls">
|
||||
<Design.PreviewWith>
|
||||
<Border>
|
||||
<TextBlock>Test</TextBlock>
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<Style Selector="Button:not(:pointerover) /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="#202020" />
|
||||
</Style>
|
||||
|
||||
|
||||
<Style Selector="Button:not(:pointerover).Transparent /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
|
||||
|
||||
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="#252525" />
|
||||
<Setter Property="BorderBrush" Value="LightGray" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
</Style>
|
||||
|
||||
|
||||
<Style Selector="controls|TagView Border">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderBrush" Value="#121212" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
</Style>
|
||||
|
||||
|
||||
<Style Selector="controls|TagView.ModList Border">
|
||||
<Setter Property="Background" Value="#868CFC" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls|TagView.Game Border">
|
||||
<Setter Property="Background" Value="#F686FC" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls|TagView.GameNotInstalled Border">
|
||||
<Setter Property="Background" Value="#FCBB86" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls|TagView.GameNotInstalled Border">
|
||||
<Setter Property="Background" Value="#FCBB86" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls|TagView TextBlock">
|
||||
<Setter Property="Foreground" Value="#121212" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.Settings">
|
||||
<Setter Property="BorderThickness" Value="2"></Setter>
|
||||
<Setter Property="BorderBrush" Value="DarkGray"></Setter>
|
||||
<Setter Property="CornerRadius" Value="4"></Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.StandardBorder">
|
||||
<Setter Property="BorderThickness" Value="2"></Setter>
|
||||
<Setter Property="BorderBrush" Value="DarkGray"></Setter>
|
||||
<Setter Property="CornerRadius" Value="4"></Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.Settings Grid">
|
||||
<Setter Property="Margin" Value="4"></Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.LogView ItemsControl">
|
||||
<Setter Property="Margin" Value="4"></Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.LogView > TextBlock.Title">
|
||||
<Setter Property="FontWeight" Value="Bold"></Setter>
|
||||
<Setter Property="FontSize" Value="18"></Setter>
|
||||
<Setter Property="Margin" Value="4"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Grid.LogView > Border">
|
||||
<Setter Property="Margin" Value="4"/>
|
||||
<Setter Property="BorderThickness" Value="2"></Setter>
|
||||
<Setter Property="BorderBrush" Value="DarkGray"></Setter>
|
||||
<Setter Property="CornerRadius" Value="4"></Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector=".InstalledList Border">
|
||||
<Setter Property="BorderThickness" Value="2"></Setter>
|
||||
<Setter Property="BorderBrush" Value="DarkGray"></Setter>
|
||||
<Setter Property="CornerRadius" Value="4"></Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector=".InstalledList TextBlock">
|
||||
<Setter Property="FontWeight" Value="Bold"></Setter>
|
||||
<Setter Property="FontSize" Value="18"></Setter>
|
||||
<Setter Property="Margin" Value="4"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.InstalledList">
|
||||
<Setter Property="Margin" Value="4"></Setter>
|
||||
</Style>
|
||||
|
||||
|
||||
|
||||
</Styles>
|
Before Width: | Height: | Size: 172 KiB |
@ -1,6 +0,0 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="cloud-download-alt"
|
||||
class="svg-inline--fa fa-cloud-download-alt fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 640 512">
|
||||
<path fill="currentColor"
|
||||
d="M537.6 226.6c4.1-10.7 6.4-22.4 6.4-34.6 0-53-43-96-96-96-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32c-88.4 0-160 71.6-160 160 0 2.7.1 5.4.2 8.1C40.2 219.8 0 273.2 0 336c0 79.5 64.5 144 144 144h368c70.7 0 128-57.3 128-128 0-61.9-44-113.6-102.4-125.4zm-132.9 88.7L299.3 420.7c-6.2 6.2-16.4 6.2-22.6 0L171.3 315.3c-10.1-10.1-2.9-27.3 11.3-27.3H248V176c0-8.8 7.2-16 16-16h48c8.8 0 16 7.2 16 16v112h65.4c14.2 0 21.4 17.2 11.3 27.3z"></path>
|
||||
</svg>
|
Before Width: | Height: | Size: 713 B |
Before Width: | Height: | Size: 139 KiB |
@ -1,93 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:controls="clr-namespace:Wabbajack.App.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Controls.BrowseItemView">
|
||||
<Border BorderThickness="1" Margin="10, 10, 10, 10">
|
||||
<Grid Width="540" Height="480" RowDefinitions="Auto, *, 40" ColumnDefinitions="*, Auto">
|
||||
<Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" BorderThickness="0, 0, 0, 1">
|
||||
<Grid ClipToBounds="True">
|
||||
<Image x:Name="ModListImage" />
|
||||
<Label Margin="10, 242, 0, 0" HorizontalAlignment="Left" VerticalAlignment="Top"
|
||||
x:Name="VersionText" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
|
||||
x:Name="Title"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
FontFamily="Lucida Sans"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
TextWrapping="Wrap" />
|
||||
<ProgressBar Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" x:Name="DownloadProgressBar" Height="3"
|
||||
VerticalAlignment="Bottom" Maximum="1000" />
|
||||
<ScrollViewer Grid.Row="1" Grid.Column="0" HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<TextBlock
|
||||
x:Name="Description"
|
||||
Margin="8,5"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
TextWrapping="Wrap" />
|
||||
</ScrollViewer>
|
||||
<ItemsControl Grid.Row="2" x:Name="TagsList">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:TagView />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="1" Grid.RowSpan="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Button Grid.Row="0"
|
||||
x:Name="OpenWebsiteButton"
|
||||
Width="40"
|
||||
Height="40"
|
||||
Margin="5,0"
|
||||
VerticalAlignment="Center">
|
||||
<avalonia:MaterialIcon
|
||||
Width="20"
|
||||
Height="20"
|
||||
Kind="Web" />
|
||||
</Button>
|
||||
<Button Grid.Row="1"
|
||||
x:Name="ModListContentsButton"
|
||||
Width="40"
|
||||
Height="40"
|
||||
Margin="5,0"
|
||||
VerticalAlignment="Center">
|
||||
<avalonia:MaterialIcon
|
||||
Width="20"
|
||||
Height="20"
|
||||
Kind="TableSearch" />
|
||||
</Button>
|
||||
<Button Grid.Row="2"
|
||||
x:Name="ExecuteButton"
|
||||
Width="40"
|
||||
Height="40"
|
||||
Margin="5,0"
|
||||
VerticalAlignment="Center">
|
||||
<avalonia:MaterialIcon
|
||||
Width="20"
|
||||
Height="20"
|
||||
x:Name="ExecuteIcon" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
@ -1,52 +0,0 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using Avalonia.ReactiveUI;
|
||||
using Material.Icons;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public partial class BrowseItemView : ReactiveUserControl<BrowseItemViewModel>
|
||||
{
|
||||
public BrowseItemView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.Title, view => view.Title.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.Description, view => view.Description.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Image, view => view.ModListImage.Source)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.OpenWebsiteCommand, view => view.OpenWebsiteButton)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.State, view => view.ExecuteIcon.Kind, s => StateToKind(s));
|
||||
this.BindCommand(ViewModel, vm => vm.ExecuteCommand, view => view.ExecuteButton)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Progress, view => view.DownloadProgressBar.Value,
|
||||
s => s.Value * 1000)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Tags, view => view.TagsList.Items)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
private MaterialIconKind StateToKind(ModListState modListState)
|
||||
{
|
||||
return modListState switch
|
||||
{
|
||||
ModListState.Disabled => MaterialIconKind.Error,
|
||||
ModListState.Downloaded => MaterialIconKind.PlayArrow,
|
||||
ModListState.Downloading => MaterialIconKind.LocalAreaNetworkPending,
|
||||
ModListState.NotDownloaded => MaterialIconKind.Download,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(modListState), modListState, null)
|
||||
};
|
||||
}
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.Messages;
|
||||
using Wabbajack.App.Models;
|
||||
using Wabbajack.App.ViewModels;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Downloaders;
|
||||
using Wabbajack.Downloaders.GameFile;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.DTOs.JsonConverters;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.Paths.IO;
|
||||
using Wabbajack.RateLimiter;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
using Wabbajack.VFS;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public enum ModListState
|
||||
{
|
||||
Downloaded,
|
||||
NotDownloaded,
|
||||
Downloading,
|
||||
Disabled
|
||||
}
|
||||
|
||||
public class BrowseItemViewModel : ViewModelBase, IActivatableViewModel
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly Configuration _configuration;
|
||||
private readonly DownloadDispatcher _dispatcher;
|
||||
private readonly IResource<DownloadDispatcher> _downloadLimiter;
|
||||
private readonly DTOSerializer _dtos;
|
||||
private readonly FileHashCache _hashCache;
|
||||
private readonly IResource<HttpClient> _limiter;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ModlistMetadata _metadata;
|
||||
private readonly ModListSummary _summary;
|
||||
private readonly ImageCache _imageCache;
|
||||
|
||||
public BrowseItemViewModel(ModlistMetadata metadata, ModListSummary summary, HttpClient client,
|
||||
IResource<HttpClient> limiter,
|
||||
FileHashCache hashCache, Configuration configuration, DownloadDispatcher dispatcher,
|
||||
IResource<DownloadDispatcher> downloadLimiter, GameLocator gameLocator, ImageCache imageCache,
|
||||
DTOSerializer dtos, ILogger logger)
|
||||
{
|
||||
Activator = new ViewModelActivator();
|
||||
_metadata = metadata;
|
||||
_summary = summary;
|
||||
_client = client;
|
||||
_limiter = limiter;
|
||||
_hashCache = hashCache;
|
||||
_configuration = configuration;
|
||||
_dispatcher = dispatcher;
|
||||
_downloadLimiter = downloadLimiter;
|
||||
_imageCache = imageCache;
|
||||
_logger = logger;
|
||||
_dtos = dtos;
|
||||
|
||||
var haveGame = gameLocator.IsInstalled(_metadata.Game);
|
||||
Tags = metadata.Tags
|
||||
.Select(t => new TagViewModel(t, "ModList"))
|
||||
.Prepend(new TagViewModel(_metadata.Game.MetaData().HumanFriendlyGameName,
|
||||
haveGame ? "Game" : "GameNotInstalled"))
|
||||
.ToArray();
|
||||
|
||||
OpenWebsiteCommand = ReactiveCommand.Create(() =>
|
||||
{
|
||||
Utils.OpenWebsiteInExternalBrowser(new Uri(_metadata.Links.Readme));
|
||||
});
|
||||
|
||||
ExecuteCommand = ReactiveCommand.Create(() =>
|
||||
{
|
||||
if (State == ModListState.Downloaded)
|
||||
{
|
||||
MessageBus.Current.SendMessage(new StartInstallConfiguration(ModListLocation));
|
||||
MessageBus.Current.SendMessage(new NavigateTo(typeof(InstallConfigurationViewModel)));
|
||||
}
|
||||
else
|
||||
{
|
||||
DownloadModList().FireAndForget();
|
||||
}
|
||||
},
|
||||
this.ObservableForProperty(t => t.State)
|
||||
.Select(c => c.Value != ModListState.Downloading && c.Value != ModListState.Disabled)
|
||||
.StartWith(true));
|
||||
|
||||
LoadListImage().FireAndForget();
|
||||
UpdateState().FireAndForget();
|
||||
}
|
||||
|
||||
public string Title => _metadata.ImageContainsTitle ? "" : _metadata.Title;
|
||||
public string MachineURL => _metadata.Links.MachineURL;
|
||||
public string Description => State == ModListState.Disabled ? "Disabled: Under Construction \n " + _metadata.Description : _metadata.Description;
|
||||
|
||||
public Uri ImageUri => new(_metadata.Links.ImageUri);
|
||||
|
||||
[Reactive] public IBitmap Image { get; set; }
|
||||
|
||||
[Reactive] public ModListState State { get; set; }
|
||||
|
||||
[Reactive] public ReactiveCommand<Unit, Unit> ExecuteCommand { get; set; }
|
||||
|
||||
[Reactive] public Percent Progress { get; set; }
|
||||
|
||||
public AbsolutePath ModListLocation => _configuration.ModListsDownloadLocation.Combine(_metadata.Links.MachineURL)
|
||||
.WithExtension(Ext.Wabbajack);
|
||||
|
||||
public Game Game => _metadata.Game;
|
||||
|
||||
public bool IsUtilityList => _metadata.UtilityList;
|
||||
public bool IsNSFW => _metadata.NSFW;
|
||||
|
||||
[Reactive] public TagViewModel[] Tags { get; set; }
|
||||
|
||||
|
||||
public ReactiveCommand<Unit, Unit> OpenWebsiteCommand { get; set; }
|
||||
|
||||
private async Task DownloadModList()
|
||||
{
|
||||
State = ModListState.Downloading;
|
||||
var state = _dispatcher.Parse(new Uri(_metadata.Links.Download));
|
||||
var archive = new Archive
|
||||
{
|
||||
State = state!,
|
||||
Hash = _metadata.DownloadMetadata?.Hash ?? default,
|
||||
Size = _metadata.DownloadMetadata?.Size ?? 0,
|
||||
Name = ModListLocation.FileName.ToString()
|
||||
};
|
||||
|
||||
using var job = await _downloadLimiter.Begin(state!.PrimaryKeyString, archive.Size, CancellationToken.None);
|
||||
|
||||
var hashTask = _dispatcher.Download(archive, ModListLocation, job, CancellationToken.None);
|
||||
|
||||
while (!hashTask.IsCompleted)
|
||||
{
|
||||
Progress = Percent.FactoryPutInRange(job.Current, job.Size ?? 0);
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
var hash = await hashTask;
|
||||
if (hash != _metadata.DownloadMetadata?.Hash)
|
||||
{
|
||||
_logger.LogWarning("Hash files didn't match after downloading modlist, deleting modlist");
|
||||
if (ModListLocation.FileExists())
|
||||
ModListLocation.Delete();
|
||||
}
|
||||
|
||||
_hashCache.FileHashWriteCache(ModListLocation, hash);
|
||||
|
||||
var metadataPath = ModListLocation.WithExtension(Ext.MetaData);
|
||||
await metadataPath.WriteAllTextAsync(_dtos.Serialize(_metadata));
|
||||
|
||||
Progress = Percent.Zero;
|
||||
await UpdateState();
|
||||
}
|
||||
|
||||
|
||||
public async Task LoadListImage()
|
||||
{
|
||||
Image = await _imageCache.From(ImageUri, 540, 300);
|
||||
}
|
||||
|
||||
public async Task<ModListState> GetState()
|
||||
{
|
||||
if (_metadata.ForceDown || _summary.HasFailures)
|
||||
return ModListState.Disabled;
|
||||
|
||||
var file = ModListLocation;
|
||||
if (!file.FileExists())
|
||||
return ModListState.NotDownloaded;
|
||||
|
||||
return await _hashCache.FileHashCachedAsync(file, CancellationToken.None) !=
|
||||
_metadata.DownloadMetadata?.Hash
|
||||
? ModListState.NotDownloaded
|
||||
: ModListState.Downloaded;
|
||||
}
|
||||
|
||||
public async Task UpdateState()
|
||||
{
|
||||
State = await GetState();
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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:i="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Controls.ButtonSettingTextBox">
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Button:not(:pointerover) /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="LightGray" />
|
||||
<Setter Property="CornerRadius" Value="0, 5, 5, 0" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="LightGray" />
|
||||
<Setter Property="CornerRadius" Value="0, 5, 5, 0" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox:not(:focus) /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="LightGray" />
|
||||
<Setter Property="CornerRadius" Value="5, 0, 0, 5" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox:focus /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="LightGray" />
|
||||
<Setter Property="CornerRadius" Value="5, 0, 0, 5" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid ColumnDefinitions="*, 30" Height="30">
|
||||
<TextBox Grid.Column="0" Name="Path" Height="30" x:Name="TextBox" IsEnabled="False" />
|
||||
<Button Grid.Column="1" Name="SelectButton" Height="30">
|
||||
<i:MaterialIcon Kind="Search" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,12 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public partial class ButtonSettingTextBox : UserControl
|
||||
{
|
||||
public ButtonSettingTextBox()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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:i="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Controls.FileSelectionBox">
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Button:not(:pointerover) /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="LightGray" />
|
||||
<Setter Property="CornerRadius" Value="0, 5, 5, 0" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="LightGray" />
|
||||
<Setter Property="CornerRadius" Value="0, 5, 5, 0" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox:not(:focus) /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="LightGray" />
|
||||
<Setter Property="CornerRadius" Value="5, 0, 0, 5" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBox:focus /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="LightGray" />
|
||||
<Setter Property="CornerRadius" Value="5, 0, 0, 5" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid ColumnDefinitions="*, 30" Height="30">
|
||||
<TextBox Grid.Column="0" Name="Path" Height="30" x:Name="TextBox" IsEnabled="False" />
|
||||
<Button Grid.Column="1" Name="SelectButton" Height="30">
|
||||
<i:MaterialIcon Kind="Search" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.ReactiveUI;
|
||||
using Avalonia.Threading;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public partial class FileSelectionBox : ReactiveUserControl<FileSelectionBoxViewModel>
|
||||
{
|
||||
public static readonly StyledProperty<string> AllowedExtensionsProperty =
|
||||
AvaloniaProperty.Register<FileSelectionBox, string>(nameof(AllowedExtensions));
|
||||
|
||||
public static readonly StyledProperty<bool> SelectFolderProperty =
|
||||
AvaloniaProperty.Register<FileSelectionBox, bool>(nameof(SelectFolder));
|
||||
|
||||
|
||||
public FileSelectionBox()
|
||||
{
|
||||
InitializeComponent();
|
||||
SelectButton.Command = ReactiveCommand.CreateFromTask(ShowDialog);
|
||||
}
|
||||
|
||||
private async Task ShowDialog()
|
||||
{
|
||||
if (SelectFolder)
|
||||
{
|
||||
var dialog = new OpenFolderDialog
|
||||
{
|
||||
Title = "Select a folder"
|
||||
};
|
||||
var result = await dialog.ShowAsync(App.MainWindow);
|
||||
if (result != null)
|
||||
Load(result.ToAbsolutePath());
|
||||
}
|
||||
else
|
||||
{
|
||||
var extensions = AllowedExtensions.Split(",").Select(e => e.ToString()[1..]).ToList();
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
AllowMultiple = false,
|
||||
Title = "Select a file",
|
||||
Filters = new List<FileDialogFilter>
|
||||
{
|
||||
new FileDialogFilter {Extensions = extensions, Name = "*"}
|
||||
}
|
||||
};
|
||||
var results = await dialog.ShowAsync(App.MainWindow);
|
||||
if (results != null)
|
||||
Load(results!.First().ToAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
[Reactive]
|
||||
public AbsolutePath SelectedPath { get; set; }
|
||||
|
||||
public string AllowedExtensions
|
||||
{
|
||||
get => GetValue(AllowedExtensionsProperty);
|
||||
set => SetValue(AllowedExtensionsProperty, value);
|
||||
}
|
||||
|
||||
public bool SelectFolder
|
||||
{
|
||||
get => GetValue(SelectFolderProperty);
|
||||
set => SetValue(SelectFolderProperty, value);
|
||||
}
|
||||
|
||||
public void Load(AbsolutePath path)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
TextBox.Text = path.ToString();
|
||||
SelectedPath = path;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.ViewModels;
|
||||
using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public class FileSelectionBoxViewModel : ViewModelBase
|
||||
{
|
||||
public FileSelectionBoxViewModel()
|
||||
{
|
||||
Activator = new ViewModelActivator();
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
BrowseCommand = ReactiveCommand.Create(async () =>
|
||||
{
|
||||
if (SelectFolder)
|
||||
{
|
||||
var dialog = new OpenFolderDialog
|
||||
{
|
||||
Title = "Select a folder"
|
||||
};
|
||||
var result = await dialog.ShowAsync(App.MainWindow);
|
||||
if (result != null)
|
||||
Path = result.ToAbsolutePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
var extensions = Extensions.Select(e => e.ToString()[1..]).ToList();
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
AllowMultiple = false,
|
||||
Title = "Select a file",
|
||||
Filters = new List<FileDialogFilter>
|
||||
{
|
||||
new FileDialogFilter {Extensions = extensions, Name = "*"}
|
||||
}
|
||||
};
|
||||
var results = await dialog.ShowAsync(App.MainWindow);
|
||||
if (results != null)
|
||||
Path = results!.First().ToAbsolutePath();
|
||||
}
|
||||
}).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
[Reactive] public AbsolutePath Path { get; set; }
|
||||
|
||||
[Reactive] public Extension[] Extensions { get; set; } = Array.Empty<Extension>();
|
||||
|
||||
[Reactive] public bool SelectFolder { get; set; }
|
||||
|
||||
[Reactive] public ReactiveCommand<Unit, Task> BrowseCommand { get; set; } = null!;
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Controls.GameSelectorItemView">
|
||||
<TextBlock x:Name="GameName" />
|
||||
</UserControl>
|
@ -1,19 +0,0 @@
|
||||
using System.Reactive.Disposables;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public partial class GameSelectorItemView : ReactiveUserControl<GameSelectorItemViewModel>
|
||||
{
|
||||
public GameSelectorItemView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.Name, view => view.GameName.Text)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.ViewModels;
|
||||
using Wabbajack.DTOs;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public class GameSelectorItemViewModel : ViewModelBase, IActivatableViewModel
|
||||
{
|
||||
public GameSelectorItemViewModel(Game game)
|
||||
{
|
||||
Activator = new ViewModelActivator();
|
||||
Game = game;
|
||||
Name = game.MetaData().HumanFriendlyGameName;
|
||||
}
|
||||
|
||||
[Reactive] public Game Game { get; set; }
|
||||
|
||||
[Reactive] public string Name { get; set; }
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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:i="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Controls.InstalledListView">
|
||||
<Button Name="ListButton" Classes="InstalledList" HorizontalAlignment="Stretch">
|
||||
<Border Classes="InstalledList">
|
||||
<Grid RowDefinitions="Auto, Auto Auto, Auto" ColumnDefinitions="Auto, *">
|
||||
<Image x:Name="ListImage" Grid.RowSpan="4" Grid.Row="0" Grid.Column="0" Width="270" Height="150"></Image>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" x:Name="Title" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" x:Name="Version" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" x:Name="Author"></TextBlock>
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" x:Name="InstallationPath" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Button>
|
||||
</UserControl>
|
@ -1,35 +0,0 @@
|
||||
using Avalonia.Controls.Mixins;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public partial class InstalledListView : ReactiveUserControl<InstalledListViewModel>, IActivatableView
|
||||
{
|
||||
public InstalledListView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.Image, view => view.ListImage.Source)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Name, view => view.Title.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Author, view => view.Author.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Version, view => view.Version.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.InstallPath, view => view.InstallationPath.Text,
|
||||
p => p.ToString())
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.Play, view => view.ListButton)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
using System.Reactive;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.Messages;
|
||||
using Wabbajack.App.Models;
|
||||
using Wabbajack.App.Screens;
|
||||
using Wabbajack.App.ViewModels;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.DTOs.SavedSettings;
|
||||
using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public class InstalledListViewModel : ViewModelBase
|
||||
{
|
||||
private readonly InstallationConfigurationSetting _setting;
|
||||
|
||||
public InstalledListViewModel(InstallationConfigurationSetting setting, ImageCache imageCache)
|
||||
{
|
||||
Activator = new ViewModelActivator();
|
||||
_setting = setting;
|
||||
|
||||
Play = ReactiveCommand.Create(() =>
|
||||
{
|
||||
MessageBus.Current.SendMessage(new ConfigureLauncher(InstallPath));
|
||||
MessageBus.Current.SendMessage(new NavigateTo(typeof(LauncherViewModel)));
|
||||
});
|
||||
|
||||
LoadImage(imageCache).FireAndForget();
|
||||
}
|
||||
|
||||
public async Task LoadImage(ImageCache cache)
|
||||
{
|
||||
var img = await cache.From(_setting.Install.Combine("modlist-image.png"), 270, 150);
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
Image = img;
|
||||
});
|
||||
}
|
||||
|
||||
public AbsolutePath InstallPath => _setting.Install;
|
||||
|
||||
public string Name => _setting.Metadata?.Title ?? "";
|
||||
|
||||
public string Version => _setting.Metadata?.Version?.ToString() ?? "";
|
||||
|
||||
public string Author => _setting.Metadata?.Author ?? "";
|
||||
public ReactiveCommand<Unit, Unit> Play { get; }
|
||||
|
||||
[Reactive]
|
||||
public IBitmap Image { get; set; }
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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:i="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Controls.LargeIconButton">
|
||||
<Button x:Name="Button" x:FieldModifier="public" Classes="Transparent">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<i:MaterialIcon x:Name="IconControl" Width="140" Height="140" />
|
||||
<TextBlock x:Name="TextBlock" HorizontalAlignment="Center" FontSize="28" FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
</UserControl>
|
@ -1,47 +0,0 @@
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Material.Icons;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public partial class LargeIconButton : UserControl, IActivatableView
|
||||
{
|
||||
public static readonly StyledProperty<string> TextProperty =
|
||||
AvaloniaProperty.Register<LargeIconButton, string>(nameof(Text));
|
||||
|
||||
|
||||
public static readonly StyledProperty<MaterialIconKind> IconProperty =
|
||||
AvaloniaProperty.Register<LargeIconButton, MaterialIconKind>(nameof(IconProperty));
|
||||
|
||||
public LargeIconButton()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.WhenActivated(dispose =>
|
||||
{
|
||||
this.WhenAnyValue(x => x.Icon)
|
||||
.Where(x => x != default)
|
||||
.BindTo(IconControl, x => x.Kind)
|
||||
.DisposeWith(dispose);
|
||||
|
||||
this.WhenAnyValue(x => x.Text)
|
||||
.Where(x => x != default)
|
||||
.BindTo(TextBlock, x => x.Text)
|
||||
.DisposeWith(dispose);
|
||||
});
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public MaterialIconKind Icon
|
||||
{
|
||||
get => GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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:controls="clr-namespace:Wabbajack.App.Controls"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Controls.LogView">
|
||||
<Grid RowDefinitions="Auto, *, Auto" Classes="LogView">
|
||||
<TextBlock Grid.Row="0" Classes="Title">Current Log Contents</TextBlock>
|
||||
<Border Grid.Row="1">
|
||||
<ItemsRepeater x:Name="Messages">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout></StackLayout>
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:LogViewItem />
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</Border>
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button x:Name="CopyLog">
|
||||
<avalonia:MaterialIcon Kind="ContentCopy" />
|
||||
</Button>
|
||||
<Button x:Name="OpenFolder">
|
||||
<avalonia:MaterialIcon Kind="Folder" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
@ -1,25 +0,0 @@
|
||||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Mixins;
|
||||
using Avalonia.ReactiveUI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public partial class LogView : ReactiveUserControl<LogViewModel>
|
||||
{
|
||||
public LogView()
|
||||
{
|
||||
DataContext = App.Services.GetService<LogViewModel>()!;
|
||||
InitializeComponent();
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.Messages, view => view.Messages.Items,
|
||||
items => items.Reverse().ToArray())
|
||||
.DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.CopyLogFile, view => view.CopyLog)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Controls.LogViewItem">
|
||||
<TextBlock x:Name="Message" FontSize="10" />
|
||||
</UserControl>
|
@ -1,19 +0,0 @@
|
||||
using Avalonia.Controls.Mixins;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.App.Utilities;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public partial class LogViewItem : ReactiveUserControl<LoggerProvider.ILogMessage>, IActivatableView
|
||||
{
|
||||
public LogViewItem()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.ShortMessage, view => view.Message.Text)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive;
|
||||
using Avalonia;
|
||||
using Avalonia.Input;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.Utilities;
|
||||
using Wabbajack.App.ViewModels;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public class LogViewModel : ViewModelBase, IActivatableViewModel
|
||||
{
|
||||
private readonly LoggerProvider _provider;
|
||||
|
||||
public LogViewModel(LoggerProvider provider)
|
||||
{
|
||||
Activator = new ViewModelActivator();
|
||||
_provider = provider;
|
||||
|
||||
CopyLogFile = ReactiveCommand.Create(() =>
|
||||
{
|
||||
var obj = new DataObject();
|
||||
obj.Set(DataFormats.FileNames, new List<string> {_provider.LogPath.ToString()});
|
||||
Application.Current.Clipboard.SetDataObjectAsync(obj);
|
||||
});
|
||||
}
|
||||
|
||||
public ReadOnlyObservableCollection<LoggerProvider.ILogMessage> Messages => _provider.MessageLog;
|
||||
|
||||
[Reactive] public ReactiveCommand<Unit, Unit> CopyLogFile { get; set; }
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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:i="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Controls.RemovableListItem">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button x:Name="DeleteButton">
|
||||
<i:MaterialIcon Kind="MinusCircle" />
|
||||
</Button>
|
||||
<TextBlock x:Name="Text" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
@ -1,21 +0,0 @@
|
||||
using Avalonia.Controls.Mixins;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public partial class RemovableListItem : ReactiveUserControl<RemovableItemViewModel>, IActivatableView
|
||||
{
|
||||
public RemovableListItem()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.Text, view => view.Text.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.DeleteCommand, view => view.DeleteButton)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
using System.Reactive;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.ViewModels;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public class RemovableItemViewModel : ViewModelBase
|
||||
{
|
||||
public RemovableItemViewModel()
|
||||
{
|
||||
Activator = new ViewModelActivator();
|
||||
}
|
||||
|
||||
[Reactive] public string Text { get; set; }
|
||||
|
||||
[Reactive] public ReactiveCommand<Unit, Unit> DeleteCommand { get; set; }
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Controls.ResourceView">
|
||||
<Grid RowDefinitions="Auto" ColumnDefinitions="140, 100, 140, 100">
|
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" Margin="4, 0" x:Name="ResourceName"></TextBlock>
|
||||
<TextBox Grid.Column="1" Text="32" Margin="4, 0" x:Name="MaxTasks"></TextBox>
|
||||
<TextBox Grid.Column="2" Margin="4, 0" x:Name="MaxThroughput"></TextBox>
|
||||
<TextBlock Grid.Column="3" Text="42GB" VerticalAlignment="Center" Margin="4, 0" x:Name="CurrentThroughput"></TextBlock>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,39 +0,0 @@
|
||||
using System.Reactive.Disposables;
|
||||
using Avalonia.ReactiveUI;
|
||||
using FluentFTP.Helpers;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public partial class ResourceView : ReactiveUserControl<ResourceViewModel>, IActivatableView
|
||||
{
|
||||
public ResourceView()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.Name, view => view.ResourceName.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.MaxTasks, view => view.MaxTasks.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.MaxThroughput, view => view.MaxThroughput.Text,
|
||||
l => l is 0 or long.MaxValue ? "∞" : (l / 1024 / 1024).ToString(),
|
||||
v =>
|
||||
{
|
||||
v = v.Trim();
|
||||
if (v is "0" or "∞" || v == long.MaxValue.ToString())
|
||||
{
|
||||
return long.MaxValue;
|
||||
}
|
||||
return long.TryParse(v, out var l) ? l * 1024 * 1024 : long.MaxValue;
|
||||
})
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.CurrentThroughput, view => view.CurrentThroughput.Text,
|
||||
val => val.FileSizeToString())
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Timers;
|
||||
using Avalonia.Threading;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.ViewModels;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.RateLimiter;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public class ResourceViewModel : ViewModelBase, IActivatableViewModel, IDisposable
|
||||
{
|
||||
private readonly IResource _resource;
|
||||
private readonly Timer _timer;
|
||||
|
||||
public ResourceViewModel(IResource resource)
|
||||
{
|
||||
Activator = new ViewModelActivator();
|
||||
_resource = resource;
|
||||
_timer = new Timer(250);
|
||||
|
||||
Name = resource.Name;
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
_timer.Elapsed += TimerElapsed;
|
||||
_timer.Start();
|
||||
|
||||
Disposable.Create(() =>
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer.Elapsed -= TimerElapsed;
|
||||
}).DisposeWith(disposables);
|
||||
|
||||
MaxTasks = _resource.MaxTasks;
|
||||
MaxThroughput = _resource.MaxThroughput;
|
||||
});
|
||||
}
|
||||
|
||||
[Reactive] public int MaxTasks { get; set; }
|
||||
|
||||
[Reactive] public long MaxThroughput { get; set; }
|
||||
|
||||
[Reactive] public long CurrentThroughput { get; set; }
|
||||
|
||||
[Reactive] public string Name { get; set; }
|
||||
|
||||
[Reactive] public string ThroughputHumanFriendly { get; set; }
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer.Dispose();
|
||||
}
|
||||
|
||||
private void TimerElapsed(object? sender, ElapsedEventArgs e)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
CurrentThroughput = _resource.StatusReport.Transferred;
|
||||
ThroughputHumanFriendly = _resource.StatusReport.Transferred.ToFileSizeString();
|
||||
});
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Controls.TagView">
|
||||
<Border x:Name="Border"
|
||||
Margin="5,5,0,5"
|
||||
BorderThickness="1"
|
||||
CornerRadius="7,7,7,7"
|
||||
VerticalAlignment="Center"
|
||||
Opacity="0.90">
|
||||
<TextBlock
|
||||
x:Name="Text"
|
||||
Margin="5,5,5,5"
|
||||
FontSize="10" />
|
||||
</Border>
|
||||
</UserControl>
|
@ -1,22 +0,0 @@
|
||||
using System.Reactive.Disposables;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public partial class TagView : ReactiveUserControl<TagViewModel>
|
||||
{
|
||||
public TagView()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.Name, view => view.Text.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.OneWayBind(ViewModel, vm => vm.Tag, view => view.Classes,
|
||||
c => c == null ? new Classes() : new Classes(c))
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.ViewModels;
|
||||
|
||||
namespace Wabbajack.App.Controls;
|
||||
|
||||
public class TagViewModel : ViewModelBase, IActivatableViewModel
|
||||
{
|
||||
public TagViewModel(string name, string tag)
|
||||
{
|
||||
Activator = new ViewModelActivator();
|
||||
Name = name;
|
||||
Tag = tag;
|
||||
}
|
||||
|
||||
[Reactive] public string Name { get; set; }
|
||||
|
||||
[Reactive] public string Tag { get; set; }
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
using System;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.App.Converters;
|
||||
|
||||
public class AbsoultePathBindingConverter : IBindingTypeConverter
|
||||
{
|
||||
public int GetAffinityForObjects(Type fromType, Type toType)
|
||||
{
|
||||
if (fromType == typeof(string) && toType == typeof(AbsolutePath) ||
|
||||
fromType == typeof(AbsolutePath) && toType == typeof(string))
|
||||
return 100;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool TryConvert(object? from, Type toType, object? conversionHint, out object? result)
|
||||
{
|
||||
switch (from)
|
||||
{
|
||||
case string s:
|
||||
result = (AbsolutePath) s;
|
||||
return true;
|
||||
case AbsolutePath ap:
|
||||
result = ap.ToString();
|
||||
return true;
|
||||
default:
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.App.Controls;
|
||||
using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.App.Extensions;
|
||||
|
||||
public static class IObservableExtensions
|
||||
{
|
||||
public static IDisposable SimpleOneWayBind<TView, TViewModel, TProp, TOut>(
|
||||
this TView view,
|
||||
TViewModel? viewModel,
|
||||
Expression<Func<TViewModel, TProp?>> vmProperty,
|
||||
Expression<Func<TView, TOut?>> viewProperty)
|
||||
where TView : class
|
||||
{
|
||||
var d = viewModel.WhenAny(vmProperty, change => change.Value)
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.BindTo(view, viewProperty);
|
||||
|
||||
return Disposable.Create(() => d.Dispose());
|
||||
}
|
||||
|
||||
public static IDisposable SimpleOneWayBind<TView, TViewModel, TProp, TOut>(
|
||||
this TView view,
|
||||
TViewModel? viewModel,
|
||||
Expression<Func<TViewModel, TProp?>> vmProperty,
|
||||
Expression<Func<TView, TOut?>> viewProperty,
|
||||
Func<TProp?, TOut> selector)
|
||||
where TView : class
|
||||
{
|
||||
var d = viewModel.WhenAnyValue(vmProperty)
|
||||
.Select(change => selector(change))
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.BindTo(view, viewProperty);
|
||||
|
||||
return Disposable.Create(() => d.Dispose());
|
||||
}
|
||||
|
||||
public static IDisposable BindFileSelectionBox<TViewModel>(this FileSelectionBox box, TViewModel viewModel,
|
||||
Expression<Func<TViewModel, AbsolutePath>> vmProperty)
|
||||
where TViewModel: class?
|
||||
{
|
||||
var disposables = new CompositeDisposable();
|
||||
|
||||
box.WhenAnyValue(view => view.SelectedPath)
|
||||
.BindTo(viewModel, vmProperty)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
viewModel.WhenAnyValue(vmProperty)
|
||||
.Where(p => p != default)
|
||||
.Subscribe(box.Load)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
return disposables;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using Avalonia.Threading;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Wabbajack.App.Extensions;
|
||||
|
||||
public static class ReactiveUIExtensions
|
||||
{
|
||||
public static IObservable<T> OnUIThread<T>(this IObservable<T> src)
|
||||
{
|
||||
return src.ObserveOn(AvaloniaScheduler.Instance);
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CefNet;
|
||||
using CefNet.Avalonia;
|
||||
using HtmlAgilityPack;
|
||||
using Wabbajack.DTOs.Logins;
|
||||
|
||||
namespace Wabbajack.App.Extensions;
|
||||
|
||||
public static class WebViewExtensions
|
||||
{
|
||||
public static async Task WaitForReady(this WebView view)
|
||||
{
|
||||
while (view.BrowserObject == null) await Task.Delay(200);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the URL and waits until the page is finished loading
|
||||
/// </summary>
|
||||
/// <param name="view"></param>
|
||||
/// <param name="uri"></param>
|
||||
public static async Task NavigateTo(this WebView view, Uri uri)
|
||||
{
|
||||
view.Navigate(uri.ToString());
|
||||
while (view.IsBusy) await Task.Delay(200);
|
||||
}
|
||||
|
||||
public static async Task<Cookie[]> Cookies(this WebView view, string domainEnding, CancellationToken token)
|
||||
{
|
||||
var results = CefCookieManager.GetGlobalManager(null)!;
|
||||
var cookies = await results.GetCookiesAsync(c => c.Domain.EndsWith(domainEnding), token)!;
|
||||
return cookies.Select(c => new Cookie
|
||||
{
|
||||
Domain = c.Domain,
|
||||
Name = c.Name,
|
||||
Path = c.Path,
|
||||
Value = c.Value
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
public static async Task EvaluateJavaScript(this WebView view, string js)
|
||||
{
|
||||
view.GetMainFrame().ExecuteJavaScript(js, "", 0);
|
||||
}
|
||||
|
||||
public static async Task<HtmlDocument> GetDom(this WebView view, CancellationToken token)
|
||||
{
|
||||
var source = await view.GetMainFrame().GetSourceAsync(token);
|
||||
var doc = new HtmlDocument();
|
||||
doc.LoadHtml(source);
|
||||
return doc;
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace Wabbajack.App;
|
||||
|
||||
public class FluentWindow : Window, IStyleable
|
||||
{
|
||||
public FluentWindow()
|
||||
{
|
||||
ExtendClientAreaToDecorationsHint = true;
|
||||
ExtendClientAreaTitleBarHeightHint = -1;
|
||||
|
||||
TransparencyLevelHint = WindowTransparencyLevel.AcrylicBlur;
|
||||
|
||||
this.GetObservable(WindowStateProperty)
|
||||
.Subscribe(x =>
|
||||
{
|
||||
PseudoClasses.Set(":maximized", x == WindowState.Maximized);
|
||||
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen);
|
||||
});
|
||||
|
||||
this.GetObservable(IsExtendedIntoWindowDecorationsProperty)
|
||||
.Subscribe(x =>
|
||||
{
|
||||
if (!x)
|
||||
{
|
||||
SystemDecorations = SystemDecorations.Full;
|
||||
TransparencyLevelHint = WindowTransparencyLevel.Blur;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Type IStyleable.StyleKey => typeof(Window);
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
ExtendClientAreaChromeHints =
|
||||
ExtendClientAreaChromeHints.PreferSystemChrome |
|
||||
ExtendClientAreaChromeHints.OSXThickTitleBar;
|
||||
}
|
||||
}
|
@ -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,8 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Wabbajack.App.Interfaces;
|
||||
|
||||
public interface INavigationParameter<T>
|
||||
{
|
||||
public Task NavigatedTo(T param);
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Wabbajack.App.Interfaces;
|
||||
|
||||
public interface IScreenView
|
||||
{
|
||||
public Type ViewModelType { get; }
|
||||
public string HumanName { get; }
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.App.Messages;
|
||||
|
||||
public record ConfigureLauncher(AbsolutePath InstallFolder)
|
||||
{
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Wabbajack.App.Messages;
|
||||
|
||||
public record Error(string Prefix, Exception Exception)
|
||||
{
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
namespace Wabbajack.App.Messages;
|
||||
|
||||
public class NavigateBack
|
||||
{
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Wabbajack.App.Messages;
|
||||
|
||||
public record NavigateTo(Type ViewModel)
|
||||
{
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
using Wabbajack.Compiler;
|
||||
|
||||
namespace Wabbajack.App.Messages;
|
||||
|
||||
public record StartCompilation(CompilerSettings Settings)
|
||||
{
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.App.Messages;
|
||||
|
||||
public record StartInstallConfiguration(AbsolutePath ModList)
|
||||
{
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.App.Messages;
|
||||
|
||||
public record StartInstallation(AbsolutePath ModListPath, AbsolutePath Install, AbsolutePath Download,
|
||||
ModlistMetadata? Metadata)
|
||||
{
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using SkiaSharp;
|
||||
using Wabbajack.Hashing.xxHash64;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.Paths.IO;
|
||||
using Wabbajack.RateLimiter;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
using Wabbajack.VFS;
|
||||
|
||||
namespace Wabbajack.App.Models;
|
||||
|
||||
public class ImageCache
|
||||
{
|
||||
private readonly Configuration _configuration;
|
||||
private readonly HttpClient _client;
|
||||
private readonly IResource<HttpClient> _limiter;
|
||||
private readonly FileHashCache _hashCache;
|
||||
|
||||
public ImageCache(Configuration configuration, HttpClient client, IResource<HttpClient> limiter, FileHashCache hashCache)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_configuration.ImageCacheLocation.CreateDirectory();
|
||||
_client = client;
|
||||
_limiter = limiter;
|
||||
_hashCache = hashCache;
|
||||
}
|
||||
|
||||
public async Task<IBitmap> From(Uri uri, int width, int height)
|
||||
{
|
||||
var hash = (await Encoding.UTF8.GetBytes(uri.ToString()).Hash()).ToHex();
|
||||
var file = _configuration.ImageCacheLocation.Combine(hash + $"_{width}_{height}");
|
||||
|
||||
if (!file.FileExists())
|
||||
{
|
||||
using var job = await _limiter.Begin("Loading Image", 0, CancellationToken.None);
|
||||
|
||||
var wdata = await _client.GetByteArrayAsync(uri);
|
||||
var resized = SKBitmap.Decode(wdata).Resize(new SKSizeI(width, height), SKFilterQuality.High);
|
||||
await file.WriteAllBytesAsync(resized.Encode(SKEncodedImageFormat.Webp, 90).ToArray());
|
||||
}
|
||||
|
||||
var data = await file.ReadAllBytesAsync();
|
||||
return new Bitmap(new MemoryStream(data));
|
||||
}
|
||||
|
||||
public async Task<IBitmap> From(AbsolutePath image, int width, int height)
|
||||
{
|
||||
var hash = await _hashCache.FileHashCachedAsync(image, CancellationToken.None);
|
||||
var file = _configuration.ImageCacheLocation.Combine(hash + $"_{width}_{height}");
|
||||
|
||||
if (!file.FileExists())
|
||||
{
|
||||
var resized = SKBitmap.Decode(image.ToString()).Resize(new SKSizeI(width, height), SKFilterQuality.High);
|
||||
await file.WriteAllBytesAsync(resized.Encode(SKEncodedImageFormat.Webp, 90).ToArray());
|
||||
}
|
||||
|
||||
var data = await file.ReadAllBytesAsync();
|
||||
return new Bitmap(new MemoryStream(data));
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Wabbajack.DTOs.JsonConverters;
|
||||
using Wabbajack.DTOs.SavedSettings;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.Paths.IO;
|
||||
|
||||
namespace Wabbajack.App.Models;
|
||||
|
||||
public class InstallationStateManager
|
||||
{
|
||||
private readonly DTOSerializer _dtos;
|
||||
private readonly ILogger<InstallationStateManager> _logger;
|
||||
|
||||
public InstallationStateManager(ILogger<InstallationStateManager> logger, DTOSerializer dtos)
|
||||
{
|
||||
_dtos = dtos;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private static AbsolutePath Path => KnownFolders.WabbajackAppLocal.Combine("install-configuration-state.json");
|
||||
|
||||
public async Task<InstallationConfigurationSetting> GetLastState()
|
||||
{
|
||||
var state = await GetAll();
|
||||
var result = state.Settings.FirstOrDefault(s => s.ModList == state.LastModlist) ??
|
||||
new InstallationConfigurationSetting();
|
||||
|
||||
if (!result.ModList.FileExists())
|
||||
return new InstallationConfigurationSetting();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task SetLastState(InstallationConfigurationSetting setting)
|
||||
{
|
||||
if (!setting.ModList.FileExists())
|
||||
{
|
||||
_logger.LogCritical("ModList path doesn't exist, not saving settings");
|
||||
return;
|
||||
}
|
||||
|
||||
var state = await GetAll();
|
||||
state.LastModlist = setting.ModList;
|
||||
state.Settings = state.Settings
|
||||
.Where(s => s.ModList != setting.ModList)
|
||||
.Append(setting)
|
||||
.ToArray();
|
||||
|
||||
await using var fs = Path.Open(FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
await _dtos.Serialize(state, fs, true);
|
||||
}
|
||||
|
||||
public async Task<InstallConfigurationState> GetAll()
|
||||
{
|
||||
if (!Path.FileExists()) return new InstallConfigurationState();
|
||||
|
||||
try
|
||||
{
|
||||
await using var fs = Path.Open(FileMode.Open);
|
||||
return (await _dtos.DeserializeAsync<InstallConfigurationState>(fs))!;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "While loading json");
|
||||
return new InstallConfigurationState();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<InstallationConfigurationSetting?> Get(AbsolutePath modListPath)
|
||||
{
|
||||
return (await GetAll()).Settings.FirstOrDefault(f => f.ModList == modListPath);
|
||||
}
|
||||
|
||||
public async Task<InstallationConfigurationSetting?> GetByInstallFolder(AbsolutePath folder)
|
||||
{
|
||||
return (await GetAll()).Settings.FirstOrDefault(f => f.Install == folder);
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using Avalonia.Threading;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
|
||||
namespace Wabbajack.App.Models;
|
||||
|
||||
public class LoadingLock : ReactiveObject, IDisposable
|
||||
{
|
||||
private readonly CompositeDisposable _disposable;
|
||||
|
||||
public LoadingLock()
|
||||
{
|
||||
_disposable = new CompositeDisposable();
|
||||
|
||||
this.WhenAnyValue(vm => vm.LoadLevel)
|
||||
.Subscribe(v => IsLoading = v > 0)
|
||||
.DisposeWith(_disposable);
|
||||
}
|
||||
|
||||
[Reactive] public int LoadLevel { get; private set; }
|
||||
|
||||
[Reactive] public bool IsLoading { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
_disposable.Dispose();
|
||||
}
|
||||
|
||||
public IDisposable WithLoading()
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => { LoadLevel++; }, DispatcherPriority.Background);
|
||||
return Disposable.Create(() =>
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => { LoadLevel--; }, DispatcherPriority.Background);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.ReactiveUI;
|
||||
using CefNet;
|
||||
|
||||
namespace Wabbajack.App;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
BuildAvaloniaApp()
|
||||
.StartWithCefNetApplicationLifetime(args);
|
||||
}
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
{
|
||||
return AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.AfterSetup(AfterSetupCallback)
|
||||
.LogToTrace()
|
||||
.UseReactiveUI();
|
||||
}
|
||||
|
||||
private static void AfterSetupCallback(AppBuilder obj)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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:controls="clr-namespace:Wabbajack.App.Controls"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Screens.BrowseView">
|
||||
<Grid RowDefinitions="40, *">
|
||||
<WrapPanel Grid.Row="0"
|
||||
Height="25"
|
||||
Margin="5,5,5,10"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<Label
|
||||
Margin="0,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Content="Game" />
|
||||
<ComboBox
|
||||
x:Name="GamesList"
|
||||
Width="150"
|
||||
Margin="0,0,10,0"
|
||||
VerticalAlignment="Center">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:GameSelectorItemView />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock
|
||||
Margin="0,0,5,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Search" />
|
||||
<TextBox
|
||||
x:Name="SearchBox"
|
||||
Width="95"
|
||||
VerticalContentAlignment="Center" x:FieldModifier="public" />
|
||||
<CheckBox
|
||||
x:Name="ShowNSFW"
|
||||
Margin="10,0,10,0"
|
||||
VerticalAlignment="Center"
|
||||
Content="Show NSFW" />
|
||||
<CheckBox
|
||||
x:Name="ShowUtilityLists"
|
||||
Margin="10,0,10,0"
|
||||
VerticalAlignment="Center"
|
||||
Content="Only Utility Lists" />
|
||||
<CheckBox
|
||||
x:Name="OnlyInstalledCheckbox"
|
||||
Margin="10,0,10,0"
|
||||
VerticalAlignment="Center"
|
||||
Content="Only Installed" />
|
||||
<Button
|
||||
x:Name="ClearFiltersButton"
|
||||
Margin="0,0,10,0">
|
||||
<avalonia:MaterialIcon Kind="FilterRemove" />
|
||||
</Button>
|
||||
</WrapPanel>
|
||||
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<ItemsRepeater x:Name="GalleryList" x:FieldModifier="public">
|
||||
<ItemsRepeater.Layout>
|
||||
<UniformGridLayout></UniformGridLayout>
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:BrowseItemView />
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,39 +0,0 @@
|
||||
using System.Reactive.Disposables;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.App.Views;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public partial class BrowseView : ScreenBase<BrowseViewModel>
|
||||
{
|
||||
public BrowseView() : base("Web Browser")
|
||||
{
|
||||
InitializeComponent();
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.ModLists, view => view.GalleryList.Items)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SearchText, view => view.SearchBox.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.GamesList, view => view.GamesList.Items)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.SelectedGame, view => view.GamesList.SelectedItem)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.ResetFiltersCommand, view => view.ClearFiltersButton)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.OnlyInstalledGames, view => view.OnlyInstalledCheckbox.IsChecked)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.OnlyUtilityLists, view => view.ShowUtilityLists.IsChecked)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.ShowNSFW, view => view.ShowNSFW.IsChecked)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,266 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using DynamicData;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.Controls;
|
||||
using Wabbajack.App.Models;
|
||||
using Wabbajack.App.ViewModels;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Downloaders;
|
||||
using Wabbajack.Downloaders.GameFile;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.DTOs.JsonConverters;
|
||||
using Wabbajack.Networking.WabbajackClientApi;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.Paths.IO;
|
||||
using Wabbajack.RateLimiter;
|
||||
using Wabbajack.VFS;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public class BrowseViewModel : ViewModelBase, IActivatableViewModel
|
||||
{
|
||||
private readonly Wabbajack.Services.OSIntegrated.Configuration _configuration;
|
||||
private readonly DownloadDispatcher _dispatcher;
|
||||
private readonly IResource<DownloadDispatcher> _dispatcherLimiter;
|
||||
private readonly DTOSerializer _dtos;
|
||||
public readonly ReadOnlyObservableCollection<GameSelectorItemViewModel> _filteredGamesList;
|
||||
|
||||
public readonly ReadOnlyObservableCollection<BrowseItemViewModel> _filteredModLists;
|
||||
private readonly GameLocator _gameLocator;
|
||||
private readonly FileHashCache _hashCache;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly IResource<HttpClient> _limiter;
|
||||
private readonly ILogger<BrowseViewModel> _logger;
|
||||
private readonly Client _wjClient;
|
||||
|
||||
private readonly SourceCache<GameSelectorItemViewModel, string> _gamesList = new(x => x.Name);
|
||||
|
||||
private readonly SourceCache<BrowseItemViewModel, string> _modLists = new(x => x.MachineURL);
|
||||
private readonly ImageCache _imageCache;
|
||||
|
||||
public BrowseViewModel(ILogger<BrowseViewModel> logger, Client wjClient, HttpClient httpClient,
|
||||
IResource<HttpClient> limiter, FileHashCache hashCache,
|
||||
IResource<DownloadDispatcher> dispatcherLimiter, DownloadDispatcher dispatcher, GameLocator gameLocator,
|
||||
ImageCache imageCache,
|
||||
DTOSerializer dtos, Wabbajack.Services.OSIntegrated.Configuration configuration)
|
||||
{
|
||||
LoadingLock = new LoadingLock();
|
||||
Activator = new ViewModelActivator();
|
||||
_wjClient = wjClient;
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
_limiter = limiter;
|
||||
_hashCache = hashCache;
|
||||
_configuration = configuration;
|
||||
_dispatcher = dispatcher;
|
||||
_dispatcherLimiter = dispatcherLimiter;
|
||||
_gameLocator = gameLocator;
|
||||
_imageCache = imageCache;
|
||||
_dtos = dtos;
|
||||
|
||||
|
||||
var searchTextPredicates = this.ObservableForProperty(vm => vm.SearchText)
|
||||
.Select(change => change.Value)
|
||||
.StartWith("")
|
||||
.Select<string, Func<BrowseItemViewModel, bool>>(txt =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(txt)) return _ => true;
|
||||
return item => item.Title.Contains(txt, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
item.Description.Contains(txt, StringComparison.InvariantCultureIgnoreCase);
|
||||
});
|
||||
|
||||
|
||||
_gamesList.Edit(e =>
|
||||
{
|
||||
e.Clear();
|
||||
foreach (var game in GameRegistry.Games.Keys) e.AddOrUpdate(new GameSelectorItemViewModel(game));
|
||||
});
|
||||
|
||||
_gamesList.Connect()
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Sort(Comparer<GameSelectorItemViewModel>.Create((a, b) => string.CompareOrdinal(a.Name, b.Name)))
|
||||
.Bind(out _filteredGamesList)
|
||||
.Subscribe();
|
||||
|
||||
var gameFilter = this.ObservableForProperty(vm => vm.SelectedGame)
|
||||
.Select(v => v.Value)
|
||||
.Select<GameSelectorItemViewModel?, Func<BrowseItemViewModel, bool>>(selected =>
|
||||
{
|
||||
if (selected == null) return _ => true;
|
||||
return item => item.Game == selected.Game;
|
||||
})
|
||||
.StartWith(_ => true);
|
||||
|
||||
var onlyInstalledGamesFilter = this.ObservableForProperty(vm => vm.OnlyInstalledGames)
|
||||
.Select(v => v.Value)
|
||||
.Select<bool, Func<BrowseItemViewModel, bool>>(onlyInstalled =>
|
||||
{
|
||||
if (onlyInstalled == false) return _ => true;
|
||||
return item => _gameLocator.IsInstalled(item.Game);
|
||||
})
|
||||
.StartWith(_ => true);
|
||||
|
||||
var onlyUtilityListsFilter = this.ObservableForProperty(vm => vm.OnlyUtilityLists)
|
||||
.Select(v => v.Value)
|
||||
.Select<bool, Func<BrowseItemViewModel, bool>>(utility =>
|
||||
{
|
||||
if (utility == false) return item => item.IsUtilityList == false;
|
||||
return item => item.IsUtilityList;
|
||||
})
|
||||
.StartWith(item => item.IsUtilityList == false);
|
||||
|
||||
var showNSFWFilter = this.ObservableForProperty(vm => vm.ShowNSFW)
|
||||
.Select(v => v.Value)
|
||||
.Select<bool, Func<BrowseItemViewModel, bool>>(showNSFW => { return item => item.IsNSFW == showNSFW; })
|
||||
.StartWith(item => item.IsNSFW == false);
|
||||
|
||||
|
||||
_modLists.Connect()
|
||||
.ObserveOn(RxApp.MainThreadScheduler)
|
||||
.Filter(searchTextPredicates)
|
||||
.Filter(gameFilter)
|
||||
.Filter(onlyInstalledGamesFilter)
|
||||
.Filter(onlyUtilityListsFilter)
|
||||
.Filter(showNSFWFilter)
|
||||
.SortBy(x => x.State == ModListState.Disabled ? 1 : 0)
|
||||
.Bind(out _filteredModLists)
|
||||
.Subscribe();
|
||||
|
||||
ResetFiltersCommand = ReactiveCommand.Create(() =>
|
||||
{
|
||||
SelectedGame = null;
|
||||
SearchText = "";
|
||||
});
|
||||
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
LoadSettings().FireAndForget();
|
||||
LoadData().FireAndForget();
|
||||
|
||||
Disposable.Create(() => { SaveSettings().FireAndForget(); }).DisposeWith(disposables);
|
||||
|
||||
/*
|
||||
var searchTextFilter = this.ObservableForProperty(view => view.SearchText)
|
||||
.Select<IObservedChange<BrowseViewModel, string>, Func<>>(text =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text.Value))
|
||||
return lst => true;
|
||||
return
|
||||
})*/
|
||||
});
|
||||
}
|
||||
|
||||
public ReadOnlyObservableCollection<BrowseItemViewModel> ModLists => _filteredModLists;
|
||||
public ReadOnlyObservableCollection<GameSelectorItemViewModel> GamesList => _filteredGamesList;
|
||||
|
||||
[Reactive] public GameSelectorItemViewModel? SelectedGame { get; set; }
|
||||
|
||||
[Reactive] public string SearchText { get; set; }
|
||||
|
||||
[Reactive] public bool OnlyInstalledGames { get; set; }
|
||||
|
||||
[Reactive] public bool OnlyUtilityLists { get; set; }
|
||||
|
||||
[Reactive] public bool ShowNSFW { get; set; }
|
||||
|
||||
[Reactive] public bool IsLoading { get; set; } = false;
|
||||
|
||||
[Reactive] public LoadingLock LoadingLock { get; set; }
|
||||
|
||||
|
||||
[Reactive] public ReactiveCommand<Unit, Unit> ResetFiltersCommand { get; set; }
|
||||
|
||||
private AbsolutePath SavedSettingsLocation => _configuration.SavedSettingsLocation.Combine("browse_view.json");
|
||||
|
||||
private async Task LoadData()
|
||||
{
|
||||
using var _ = LoadingLock.WithLoading();
|
||||
var modlists = await _wjClient.LoadLists();
|
||||
var summaries = (await _wjClient.GetListStatuses()).ToDictionary(m => m.MachineURL);
|
||||
var vms = modlists.Select(m =>
|
||||
{
|
||||
if (!summaries.TryGetValue(m.Links.MachineURL, out var summary)) summary = new ModListSummary();
|
||||
|
||||
return new BrowseItemViewModel(m, summary, _httpClient, _limiter, _hashCache, _configuration, _dispatcher,
|
||||
_dispatcherLimiter, _gameLocator, _imageCache, _dtos, _logger);
|
||||
});
|
||||
|
||||
_modLists.Edit(lsts =>
|
||||
{
|
||||
lsts.Clear();
|
||||
lsts.AddOrUpdate(vms);
|
||||
});
|
||||
|
||||
_logger.LogInformation("Loaded data for {Count} modlists", _modLists.Count);
|
||||
}
|
||||
|
||||
private async Task LoadSettings()
|
||||
{
|
||||
using var _ = LoadingLock.WithLoading();
|
||||
try
|
||||
{
|
||||
if (SavedSettingsLocation.FileExists())
|
||||
{
|
||||
await using var stream = SavedSettingsLocation.Open(FileMode.Open);
|
||||
var data = (await JsonSerializer.DeserializeAsync<SavedSettings>(stream))!;
|
||||
SearchText = data.SearchText;
|
||||
|
||||
SelectedGame = data.SelectedGame == null
|
||||
? null
|
||||
: _gamesList.Lookup(data.SelectedGame.Value.MetaData().HumanFriendlyGameName).Value;
|
||||
|
||||
ShowNSFW = data.ShowNSFW;
|
||||
OnlyUtilityLists = data.OnlyUtility;
|
||||
OnlyInstalledGames = data.OnlyInstalled;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "While loading gallery browse settings");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = new SavedSettings
|
||||
{
|
||||
SearchText = SearchText,
|
||||
OnlyInstalled = OnlyInstalledGames,
|
||||
OnlyUtility = OnlyUtilityLists,
|
||||
ShowNSFW = ShowNSFW,
|
||||
SelectedGame = SelectedGame?.Game
|
||||
};
|
||||
SavedSettingsLocation.Parent.CreateDirectory();
|
||||
await using var stream = SavedSettingsLocation.Open(FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
await JsonSerializer.SerializeAsync(stream, settings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "While saving gallery browse settings");
|
||||
}
|
||||
}
|
||||
|
||||
private class SavedSettings
|
||||
{
|
||||
public string SearchText { get; set; }
|
||||
public bool ShowNSFW { get; set; }
|
||||
public bool OnlyUtility { get; set; }
|
||||
public bool OnlyInstalled { get; set; }
|
||||
public Game? SelectedGame { get; set; }
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:controls="clr-namespace:Wabbajack.App.Controls"
|
||||
x:Class="Wabbajack.App.Screens.CompilationView">
|
||||
<Grid RowDefinitions="40, 5, 5, *, 40">
|
||||
<TextBlock Grid.Row="0" x:Name="StatusText" FontSize="20" FontWeight="Bold">[20/30] Installing Files</TextBlock>
|
||||
<ProgressBar Grid.Row="1" x:Name="StepsProgress" Maximum="1000" Value="40" />
|
||||
<ProgressBar Grid.Row="2" x:Name="StepProgress" Maximum="10000" Value="30" />
|
||||
<controls:LogView Grid.Row="3" x:Name="LogView" />
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,25 +0,0 @@
|
||||
using Avalonia.Controls.Mixins;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.App.Views;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public partial class CompilationView : ScreenBase<CompilationViewModel>
|
||||
{
|
||||
public CompilationView() : base("Compiling")
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.StatusText, view => view.StatusText.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.StepsProgress, view => view.StepsProgress.Value, p => p.Value * 1000)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.StepProgress, view => view.StepProgress.Value, p => p.Value * 10000)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Threading;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.Messages;
|
||||
using Wabbajack.App.ViewModels;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Compiler;
|
||||
using Wabbajack.RateLimiter;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public class CompilationViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ILogger<CompilationViewModel> _logger;
|
||||
private readonly IServiceProvider _provider;
|
||||
private ACompiler _compiler;
|
||||
|
||||
|
||||
public CompilationViewModel(ILogger<CompilationViewModel> logger, IServiceProvider provider)
|
||||
{
|
||||
_logger = logger;
|
||||
_provider = provider;
|
||||
Activator = new ViewModelActivator();
|
||||
|
||||
MessageBus.Current.Listen<StartCompilation>()
|
||||
.Subscribe(Receive)
|
||||
.DisposeWith(VMDisposables);
|
||||
}
|
||||
|
||||
[Reactive] public string StatusText { get; set; } = "";
|
||||
[Reactive] public Percent StepsProgress { get; set; } = Percent.Zero;
|
||||
[Reactive] public Percent StepProgress { get; set; } = Percent.Zero;
|
||||
|
||||
private void Receive(StartCompilation val)
|
||||
{
|
||||
if (val.Settings is MO2CompilerSettings mo2)
|
||||
{
|
||||
var compiler = _provider.GetService<MO2Compiler>()!;
|
||||
compiler.Settings = mo2;
|
||||
_compiler = compiler;
|
||||
_compiler.OnStatusUpdate += (sender, update) =>
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
StatusText = update.StatusText;
|
||||
StepsProgress = update.StepsProgress;
|
||||
StepProgress = update.StepProgress;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Compile().FireAndForget();
|
||||
}
|
||||
|
||||
public async Task Compile()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await Task.Run(async () => await _compiler.Begin(CancellationToken.None));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "During Compilation: {Message}", ex.Message);
|
||||
StatusText = $"ERRORED: {ex.Message}";
|
||||
ErrorPageViewModel.Display("During compilation", ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:i="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:controls="clr-namespace:Wabbajack.App.Controls"
|
||||
x:Class="Wabbajack.App.Screens.CompilerConfigurationView">
|
||||
<Grid RowDefinitions="40, *, 40" Margin="8">
|
||||
<TextBlock Grid.Row="0" x:Name="StatusText" FontSize="20" FontWeight="Bold">Compiler Configuration</TextBlock>
|
||||
<Grid Grid.Row="1" ColumnDefinitions="Auto, *" RowDefinitions="Auto, Auto, Auto, Auto, Auto, Auto, Auto, Auto"
|
||||
Margin="4">
|
||||
<Label Grid.Column="0" Grid.Row="0" HorizontalAlignment="Right">Title:</Label>
|
||||
<TextBox Grid.Column="1" Grid.Row="0" x:Name="Title" />
|
||||
<Label Grid.Column="0" Grid.Row="1" HorizontalAlignment="Right">Settings File:</Label>
|
||||
<controls:FileSelectionBox Grid.Column="1" Grid.Row="1" x:Name="SettingsFile"
|
||||
AllowedExtensions=".txt|.json" />
|
||||
<Label Grid.Column="0" Grid.Row="2" HorizontalAlignment="Right">Source:</Label>
|
||||
<controls:FileSelectionBox Grid.Column="1" Grid.Row="2" x:Name="Source" SelectFolder="True" />
|
||||
<Label Grid.Column="0" Grid.Row="3" HorizontalAlignment="Right">Downloads Folder:</Label>
|
||||
<controls:FileSelectionBox Grid.Column="1" Grid.Row="3" x:Name="DownloadsFolder" SelectFolder="True" />
|
||||
<Label Grid.Column="0" Grid.Row="4" HorizontalAlignment="Right">Base Game:</Label>
|
||||
<ComboBox Grid.Column="1" Grid.Row="4" x:Name="BaseGame">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=HumanFriendlyGameName}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<Label Grid.Column="0" Grid.Row="5" HorizontalAlignment="Right">Output Folder:</Label>
|
||||
<controls:FileSelectionBox Grid.Column="1" Grid.Row="5" x:Name="OutputFolder" SelectFolder="True" />
|
||||
|
||||
<Label Grid.Column="0" Grid.Row="6" HorizontalAlignment="Right" VerticalAlignment="Top">Other Profiles:</Label>
|
||||
<StackPanel Grid.Column="1" Grid.Row="6" Orientation="Vertical">
|
||||
<Button x:Name="AddOtherProfile">
|
||||
<i:MaterialIcon Kind="AddCircle" />
|
||||
</Button>
|
||||
<ItemsControl x:Name="OtherProfilesList">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:RemovableListItem />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
<Label Grid.Column="0" Grid.Row="7" HorizontalAlignment="Right" VerticalAlignment="Top">Always Enabled:</Label>
|
||||
<StackPanel Grid.Column="1" Grid.Row="7" Orientation="Vertical">
|
||||
<Button x:Name="AddAlwaysEnabled">
|
||||
<i:MaterialIcon Kind="AddCircle" />
|
||||
</Button>
|
||||
<ItemsControl x:Name="AlwaysEnabledList">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:RemovableListItem />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
|
||||
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="*, Auto, Auto" Grid.Row="2">
|
||||
<Button Grid.Column="1" x:Name="InferSettings" Click="InferSettings_OnClick">
|
||||
<TextBlock>Infer Settings</TextBlock>
|
||||
</Button>
|
||||
<Button Grid.Column="2" x:Name="StartCompilation">
|
||||
<TextBlock>Start Compilation</TextBlock>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,107 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Threading;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.App.Controls;
|
||||
using Wabbajack.App.Extensions;
|
||||
using Wabbajack.App.Views;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Paths;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public partial class CompilerConfigurationView : ScreenBase<CompilerConfigurationViewModel>
|
||||
{
|
||||
public CompilerConfigurationView() : base("Compiler Configuration")
|
||||
{
|
||||
InitializeComponent();
|
||||
AddAlwaysEnabled.Command = ReactiveCommand.Create(() => AddAlwaysEnabled_Command().FireAndForget());
|
||||
AddOtherProfile.Command = ReactiveCommand.Create(() => AddOtherProfile_Command().FireAndForget());
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.Bind(ViewModel, vm => vm.Title, view => view.Title.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
SettingsFile.BindFileSelectionBox(ViewModel, vm => vm.SettingsFile)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
Source.BindFileSelectionBox(ViewModel, vm => vm.Source)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
DownloadsFolder.BindFileSelectionBox(ViewModel, vm => vm.Downloads)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
OutputFolder.BindFileSelectionBox(ViewModel, vm => vm.OutputFolder)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.AllGames, view => view.BaseGame.Items)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.Bind(ViewModel, vm => vm.BaseGame, view => view.BaseGame.SelectedItem)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.StartCompilation, view => view.StartCompilation)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.AlwaysEnabled, view => view.AlwaysEnabledList.Items,
|
||||
d => d!.Select(itm => new RemovableItemViewModel
|
||||
{
|
||||
Text = itm.ToString(),
|
||||
DeleteCommand = ReactiveCommand.Create(() => { ViewModel?.RemoveAlwaysExcluded(itm); })
|
||||
}))
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.OtherProfiles, view => view.OtherProfilesList.Items,
|
||||
d => d!.Select(itm => new RemovableItemViewModel
|
||||
{
|
||||
Text = itm.ToString(),
|
||||
DeleteCommand = ReactiveCommand.Create(() => { ViewModel?.RemoveOtherProfile(itm); })
|
||||
}))
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task AddOtherProfile_Command()
|
||||
{
|
||||
var dialog = new OpenFolderDialog
|
||||
{
|
||||
Title = "Select a profile folder"
|
||||
};
|
||||
var result = await dialog.ShowAsync(App.MainWindow);
|
||||
if (!string.IsNullOrWhiteSpace(result))
|
||||
ViewModel!.AddOtherProfile(result.ToAbsolutePath());
|
||||
}
|
||||
|
||||
private async Task AddAlwaysEnabled_Command()
|
||||
{
|
||||
var dialog = new OpenFolderDialog
|
||||
{
|
||||
Title = "Select a folder"
|
||||
};
|
||||
var result = await dialog.ShowAsync(App.MainWindow);
|
||||
if (!string.IsNullOrWhiteSpace(result))
|
||||
ViewModel!.AddAlwaysExcluded(result.ToAbsolutePath());
|
||||
}
|
||||
|
||||
private void InferSettings_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Title = "Select a modlist.txt file",
|
||||
Filters = new List<FileDialogFilter>
|
||||
{new() {Extensions = new List<string> {"txt"}, Name = "modlist.txt"}},
|
||||
AllowMultiple = false
|
||||
};
|
||||
var result = await dialog.ShowAsync(App.MainWindow);
|
||||
if (result is {Length: > 0})
|
||||
await ViewModel!.InferSettingsFromModlistTxt(result.First().ToAbsolutePath());
|
||||
});
|
||||
}
|
||||
}
|
@ -1,240 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls.Mixins;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.Messages;
|
||||
using Wabbajack.App.Models;
|
||||
using Wabbajack.App.ViewModels;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Compiler;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.DTOs.JsonConverters;
|
||||
using Wabbajack.Installer;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.Paths.IO;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
using Consts = Wabbajack.Compiler.Consts;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public class CompilerConfigurationViewModel : ViewModelBase
|
||||
{
|
||||
private readonly DTOSerializer _dtos;
|
||||
private readonly SettingsManager _settingsManager;
|
||||
|
||||
|
||||
public CompilerConfigurationViewModel(DTOSerializer dtos, SettingsManager settingsManager)
|
||||
{
|
||||
_settingsManager = settingsManager;
|
||||
_dtos = dtos;
|
||||
Activator = new ViewModelActivator();
|
||||
|
||||
AllGames = GameRegistry.Games.Values.ToArray();
|
||||
|
||||
StartCompilation = ReactiveCommand.Create(() => BeginCompilation().FireAndForget());
|
||||
|
||||
OutputFolder = KnownFolders.EntryPoint;
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
LoadLastCompilation().FireAndForget();
|
||||
this.WhenAnyValue(v => v.SettingsFile)
|
||||
.Subscribe(location => { LoadNewSettingsFile(location).FireAndForget(); })
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
[Reactive] public string Title { get; set; }
|
||||
|
||||
[Reactive] public AbsolutePath SettingsFile { get; set; }
|
||||
|
||||
[Reactive] public AbsolutePath Downloads { get; set; }
|
||||
|
||||
[Reactive] public GameMetaData BaseGame { get; set; }
|
||||
|
||||
[Reactive] public AbsolutePath Source { get; set; }
|
||||
|
||||
[Reactive] public AbsolutePath GamePath { get; set; }
|
||||
|
||||
[Reactive] public string SelectedProfile { get; set; }
|
||||
|
||||
[Reactive] public AbsolutePath OutputFolder { get; set; }
|
||||
|
||||
[Reactive] public IEnumerable<GameMetaData> AllGames { get; set; }
|
||||
|
||||
[Reactive] public ReactiveCommand<Unit, Unit> StartCompilation { get; set; }
|
||||
|
||||
[Reactive] public IEnumerable<RelativePath> AlwaysEnabled { get; set; } = Array.Empty<RelativePath>();
|
||||
|
||||
[Reactive]
|
||||
public string[] OtherProfiles { get; set; } = Array.Empty<string>();
|
||||
|
||||
public AbsolutePath SettingsOutputLocation => Source.Combine(Title)
|
||||
.WithExtension(IsMO2Compilation ? Ext.MO2CompilerSettings : Ext.CompilerSettings);
|
||||
|
||||
[Reactive] public bool IsMO2Compilation { get; set; }
|
||||
|
||||
private async Task LoadNewSettingsFile(AbsolutePath location)
|
||||
{
|
||||
if (location == default) return;
|
||||
if (location.FileExists()) await LoadSettings(location);
|
||||
}
|
||||
|
||||
private async Task LoadLastCompilation()
|
||||
{
|
||||
var location = await _settingsManager.Load<AbsolutePath>("last_compilation");
|
||||
SettingsFile = location;
|
||||
}
|
||||
|
||||
private async Task BeginCompilation()
|
||||
{
|
||||
var settings = GetSettings();
|
||||
await SaveSettingsFile();
|
||||
await _settingsManager.Save("last_compilation", SettingsOutputLocation);
|
||||
|
||||
MessageBus.Current.SendMessage(new StartCompilation(settings));
|
||||
MessageBus.Current.SendMessage(new NavigateTo(typeof(CompilationViewModel)));
|
||||
}
|
||||
|
||||
private CompilerSettings GetSettings()
|
||||
{
|
||||
return new MO2CompilerSettings
|
||||
{
|
||||
ModListName = Title,
|
||||
Downloads = Downloads,
|
||||
Source = Source,
|
||||
Game = BaseGame.Game,
|
||||
Profile = SelectedProfile,
|
||||
UseGamePaths = true,
|
||||
OutputFile = OutputFolder.Combine(SelectedProfile).WithExtension(Ext.Wabbajack),
|
||||
AlwaysEnabled = AlwaysEnabled.ToArray(),
|
||||
OtherProfiles = OtherProfiles.ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
public bool AddOtherProfile(AbsolutePath path)
|
||||
{
|
||||
if (!path.InFolder(Source.Combine(Consts.MO2Profiles))) return false;
|
||||
var relative = path.RelativeTo(Source.Combine(Consts.MO2Profiles)).ToString();
|
||||
OtherProfiles = OtherProfiles.Append(relative).Distinct().ToArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveOtherProfile(string profile)
|
||||
{
|
||||
OtherProfiles = OtherProfiles.Where(p => p != profile).ToArray();
|
||||
}
|
||||
|
||||
public bool AddAlwaysExcluded(AbsolutePath path)
|
||||
{
|
||||
if (!path.InFolder(Source)) return false;
|
||||
var relative = path.RelativeTo(Source);
|
||||
AlwaysEnabled = AlwaysEnabled.Append(relative).Distinct().ToArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveAlwaysExcluded(RelativePath path)
|
||||
{
|
||||
AlwaysEnabled = AlwaysEnabled.Where(p => p != path).ToArray();
|
||||
}
|
||||
|
||||
public async Task InferSettingsFromModlistTxt(AbsolutePath settingsFile)
|
||||
{
|
||||
if (settingsFile.FileName == "modlist.txt".ToRelativePath() && settingsFile.Depth > 3)
|
||||
{
|
||||
var mo2Folder = settingsFile.Parent.Parent.Parent;
|
||||
var mo2Ini = mo2Folder.Combine(Consts.MO2IniName);
|
||||
if (mo2Ini.FileExists())
|
||||
{
|
||||
var iniData = mo2Ini.LoadIniFile();
|
||||
|
||||
var general = iniData["General"];
|
||||
|
||||
BaseGame = GameRegistry.GetByFuzzyName(general["gameName"].FromMO2Ini());
|
||||
Source = mo2Folder;
|
||||
|
||||
SelectedProfile = general["selected_profile"].FromMO2Ini();
|
||||
GamePath = general["gamePath"].FromMO2Ini().ToAbsolutePath();
|
||||
Title = SelectedProfile;
|
||||
|
||||
var settings = iniData["Settings"];
|
||||
Downloads = settings["download_directory"].FromMO2Ini().ToAbsolutePath();
|
||||
IsMO2Compilation = true;
|
||||
|
||||
|
||||
|
||||
AlwaysEnabled = Array.Empty<RelativePath>();
|
||||
// Find Always Enabled mods
|
||||
foreach (var modFolder in mo2Folder.Combine("mods").EnumerateDirectories())
|
||||
{
|
||||
var iniFile = modFolder.Combine("meta.ini");
|
||||
if (!iniFile.FileExists()) continue;
|
||||
|
||||
var data = iniFile.LoadIniFile();
|
||||
var generalModData = data["General"];
|
||||
if ((generalModData["notes"]?.Contains("WABBAJACK_ALWAYS_ENABLE") ?? false) ||
|
||||
(generalModData["comments"]?.Contains("WABBAJACK_ALWAYS_ENABLE") ?? false))
|
||||
AlwaysEnabled = AlwaysEnabled.Append(modFolder.RelativeTo(mo2Folder)).ToArray();
|
||||
}
|
||||
|
||||
var otherProfilesFile = settingsFile.Parent.Combine("otherprofiles.txt");
|
||||
if (otherProfilesFile.FileExists())
|
||||
{
|
||||
OtherProfiles = await otherProfilesFile.ReadAllLinesAsync().ToArray();
|
||||
}
|
||||
|
||||
if (mo2Folder.Depth > 1)
|
||||
OutputFolder = mo2Folder.Parent;
|
||||
|
||||
await SaveSettingsFile();
|
||||
SettingsFile = SettingsOutputLocation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async Task SaveSettingsFile()
|
||||
{
|
||||
await using var st = SettingsOutputLocation.Open(FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
if (IsMO2Compilation)
|
||||
await JsonSerializer.SerializeAsync(st, (MO2CompilerSettings) GetSettings(), _dtos.Options);
|
||||
else
|
||||
await JsonSerializer.SerializeAsync(st, GetSettings(), _dtos.Options);
|
||||
}
|
||||
|
||||
private async Task LoadSettings(AbsolutePath path)
|
||||
{
|
||||
CompilerSettings s;
|
||||
if (path.Extension == Ext.MO2CompilerSettings)
|
||||
{
|
||||
var mo2 = await LoadSettingsFile<MO2CompilerSettings>(path);
|
||||
AlwaysEnabled = mo2.AlwaysEnabled;
|
||||
OtherProfiles = mo2.OtherProfiles;
|
||||
SelectedProfile = mo2.Profile;
|
||||
s = mo2;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
Title = s.ModListName;
|
||||
Source = s.Source;
|
||||
Downloads = s.Downloads;
|
||||
OutputFolder = s.OutputFile.Depth > 1 ? s.OutputFile.Parent : s.OutputFile;
|
||||
BaseGame = s.Game.MetaData();
|
||||
}
|
||||
|
||||
private async Task<T> LoadSettingsFile<T>(AbsolutePath path)
|
||||
{
|
||||
await using var st = path.Open(FileMode.Open);
|
||||
return (await JsonSerializer.DeserializeAsync<T>(st, _dtos.Options))!;
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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:controls="clr-namespace:Wabbajack.App.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Screens.ErrorPageView">
|
||||
<Grid RowDefinitions="Auto, Auto, *">
|
||||
<TextBlock Grid.Row="0" x:Name="Prefix" />
|
||||
<TextBlock Grid.Row="1" x:Name="Message" />
|
||||
<controls:LogView Grid.Row="3" />
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,20 +0,0 @@
|
||||
using System.Reactive.Disposables;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.App.Views;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public partial class ErrorPageView : ScreenBase<ErrorPageViewModel>
|
||||
{
|
||||
public ErrorPageView() : base("Error")
|
||||
{
|
||||
InitializeComponent();
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.Bind(ViewModel, vm => vm.Prefix, view => view.Prefix.Text)
|
||||
.DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.ShortMessage, view => view.Message.Text)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
using System;
|
||||
using Avalonia.Controls.Mixins;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.Messages;
|
||||
using Wabbajack.App.ViewModels;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public class ErrorPageViewModel : ViewModelBase
|
||||
{
|
||||
public ErrorPageViewModel()
|
||||
{
|
||||
Activator = new ViewModelActivator();
|
||||
MessageBus.Current.Listen<Error>()
|
||||
.Subscribe(Receive)
|
||||
.DisposeWith(VMDisposables);
|
||||
}
|
||||
|
||||
[Reactive] public string ShortMessage { get; set; }
|
||||
|
||||
[Reactive] public string Prefix { get; set; }
|
||||
|
||||
public void Receive(Error val)
|
||||
{
|
||||
Prefix = val.Prefix;
|
||||
ShortMessage = val.Exception.Message;
|
||||
}
|
||||
|
||||
public static void Display(string prefix, Exception ex)
|
||||
{
|
||||
MessageBus.Current.SendMessage(new Error(prefix, ex));
|
||||
MessageBus.Current.SendMessage(new NavigateTo(typeof(ErrorPageViewModel)));
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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:controls="clr-namespace:Wabbajack.App.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Screens.LauncherView">
|
||||
<Grid RowDefinitions="*, Auto">
|
||||
<Viewbox Grid.Row="0"
|
||||
Name="Banner"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="UniformToFill">
|
||||
<Image x:Name="ModListImage" Margin="0,0,0,0" Source="../Assets/Wabba_Mouth.png" />
|
||||
</Viewbox>
|
||||
<Grid Grid.Row="0" RowDefinitions="40, 40" HorizontalAlignment="Left" VerticalAlignment="Bottom">
|
||||
<TextBlock x:Name="ModListName" />
|
||||
</Grid>
|
||||
<Grid Margin="40" RowDefinitions="40, 40, 40, *" ColumnDefinitions="100, *, 200" Grid.Row="1">
|
||||
<Label Grid.Column="0" Grid.Row="0" HorizontalAlignment="Right" VerticalAlignment="Center">ModList:</Label>
|
||||
<TextBox Grid.Column="1" Grid.Row="0" IsEnabled="False" Height="20" x:Name="ModList" />
|
||||
|
||||
<Label Grid.Column="0" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Center">Location:</Label>
|
||||
<TextBox Grid.Column="1" Grid.Row="1" IsEnabled="False" Height="20" x:Name="InstallPath" />
|
||||
|
||||
<Grid Grid.Column="1" Grid.Row="3" Grid.ColumnDefinitions="*, *, *" HorizontalAlignment="Center">
|
||||
<Button Grid.Column="0" x:Name="WebsiteButton" Click="ShowWebsite">Website</Button>
|
||||
<Button Grid.Column="1" x:Name="ReadmeButton" Click="ShowReadme">Readme</Button>
|
||||
<Button Grid.Column="2" x:Name="LocalFilesButton" Click="ShowLocalFiles">Local Files</Button>
|
||||
</Grid>
|
||||
|
||||
<controls:LargeIconButton x:Name="PlayGame" Margin="40, 0, 0, 0" Grid.Row="0" Grid.Column="2"
|
||||
Grid.RowSpan="4" Icon="PlayCircle" Text="Play" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,47 +0,0 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using Avalonia.Interactivity;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.App.Utilities;
|
||||
using Wabbajack.App.Views;
|
||||
using Wabbajack.Installer;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public partial class LauncherView : ScreenBase<LauncherViewModel>
|
||||
{
|
||||
public LauncherView() : base("Launch Modlist")
|
||||
{
|
||||
InitializeComponent();
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.Image, view => view.ModListImage.Source)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Title, view => view.ModList.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.InstallFolder, view => view.InstallPath.Text,
|
||||
v => v.ToString())
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.PlayButton, view => view.PlayGame.Button)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
private void ShowWebsite(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
OSUtil.OpenWebsite(ViewModel!.Setting!.StrippedModListData?.Website!);
|
||||
}
|
||||
|
||||
private void ShowReadme(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
OSUtil.OpenWebsite(new Uri(ViewModel!.Setting!.StrippedModListData?.Readme!));
|
||||
}
|
||||
|
||||
private void ShowLocalFiles(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
OSUtil.OpenFolder(ViewModel!.Setting!.Install);
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.Extensions;
|
||||
using Wabbajack.App.Messages;
|
||||
using Wabbajack.App.Models;
|
||||
using Wabbajack.App.ViewModels;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.DTOs.SavedSettings;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.Paths.IO;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public class LauncherViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ILogger<LauncherViewModel> _logger;
|
||||
|
||||
public ReactiveCommand<Unit, Unit> PlayButton;
|
||||
|
||||
public LauncherViewModel(ILogger<LauncherViewModel> logger, InstallationStateManager manager)
|
||||
{
|
||||
Activator = new ViewModelActivator();
|
||||
PlayButton = ReactiveCommand.Create(() => { StartGame().FireAndForget(); });
|
||||
_logger = logger;
|
||||
|
||||
MessageBus.Current.Listen<ConfigureLauncher>()
|
||||
.Subscribe(Receive)
|
||||
.DisposeWith(VMDisposables);
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.WhenAnyValue(v => v.InstallFolder)
|
||||
.SelectMany(async folder => await manager.GetByInstallFolder(folder))
|
||||
.OnUIThread()
|
||||
.Where(v => v != null)
|
||||
.BindTo(this, vm => vm.Setting)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.WhenAnyValue(v => v.Setting)
|
||||
.Where(v => v != default && v!.Image != default && v!.Image.FileExists())
|
||||
.Select(v => new Bitmap(v!.Image.ToString()))
|
||||
.BindTo(this, vm => vm.Image)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.WhenAnyValue(v => v.Setting)
|
||||
.Where(v => v is {Metadata: { }})
|
||||
.Select(v => $"{v!.Metadata!.Title} v{v!.Metadata.Version}")
|
||||
.BindTo(this, vm => vm.Title)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
[Reactive] public AbsolutePath InstallFolder { get; set; }
|
||||
|
||||
[Reactive] public IBitmap Image { get; set; }
|
||||
|
||||
[Reactive] public InstallationConfigurationSetting? Setting { get; set; }
|
||||
|
||||
[Reactive] public string Title { get; set; }
|
||||
|
||||
public void Receive(ConfigureLauncher val)
|
||||
{
|
||||
InstallFolder = val.InstallFolder;
|
||||
}
|
||||
|
||||
private async Task StartGame()
|
||||
{
|
||||
var mo2Path = InstallFolder.Combine("ModOrganizer.exe");
|
||||
var gamePath = GameRegistry.Games.Values.Select(g => g.MainExecutable)
|
||||
.Where(ge => ge != null)
|
||||
.Select(ge => InstallFolder.Combine(ge!))
|
||||
.FirstOrDefault(ge => ge.FileExists());
|
||||
if (mo2Path.FileExists())
|
||||
Process.Start(mo2Path.ToString());
|
||||
else if (gamePath.FileExists())
|
||||
Process.Start(gamePath.ToString());
|
||||
else
|
||||
_logger.LogError("No way to launch game, no acceptable executable found");
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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:controls="clr-namespace:Wabbajack.App.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Screens.LogScreenView">
|
||||
<Grid RowDefinitions="*">
|
||||
<controls:LogView />
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,11 +0,0 @@
|
||||
using Wabbajack.App.Views;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public partial class LogScreenView : ScreenBase<LogScreenViewModel>
|
||||
{
|
||||
public LogScreenView() : base("Application Log")
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using ReactiveUI;
|
||||
using Wabbajack.App.Utilities;
|
||||
using Wabbajack.App.ViewModels;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public class LogScreenViewModel : ViewModelBase, IActivatableViewModel
|
||||
{
|
||||
private readonly LoggerProvider _provider;
|
||||
|
||||
public LogScreenViewModel(LoggerProvider provider)
|
||||
{
|
||||
_provider = provider;
|
||||
Activator = new ViewModelActivator();
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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:controls="clr-namespace:Wabbajack.App.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Screens.PlaySelectView">
|
||||
<Grid RowDefinitions="*">
|
||||
<ItemsRepeater x:Name="Lists">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout></StackLayout>
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:InstalledListView />
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,18 +0,0 @@
|
||||
using Avalonia.Controls.Mixins;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.App.Views;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public partial class PlaySelectView : ScreenBase<PlaySelectViewModel>
|
||||
{
|
||||
public PlaySelectView() : base("Modlist Selection")
|
||||
{
|
||||
InitializeComponent();
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.Items, view => view.Lists.Items)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading.Tasks;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.Controls;
|
||||
using Wabbajack.App.Models;
|
||||
using Wabbajack.App.ViewModels;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public class PlaySelectViewModel : ViewModelBase, IActivatableViewModel
|
||||
{
|
||||
private readonly InstallationStateManager _manager;
|
||||
private readonly ImageCache _imageCache;
|
||||
|
||||
public PlaySelectViewModel(InstallationStateManager manager, ImageCache imageCache)
|
||||
{
|
||||
_imageCache = imageCache;
|
||||
_manager = manager;
|
||||
Activator = new ViewModelActivator();
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
LoadAndSetItems().FireAndForget();
|
||||
Disposable.Empty.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
[Reactive] public IEnumerable<InstalledListViewModel> Items { get; set; }
|
||||
|
||||
public async Task LoadAndSetItems()
|
||||
{
|
||||
var items = await _manager.GetAll();
|
||||
Items = items.Settings.Select(a => new InstalledListViewModel(a, _imageCache)).ToArray();
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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:controls="clr-namespace:Wabbajack.App.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Wabbajack.App.Screens.SettingsView">
|
||||
<WrapPanel Margin="40">
|
||||
<Border x:Name="LoginBorder" Margin="5" BorderThickness="1" Classes="Settings">
|
||||
<Grid RowDefinitions="Auto, Auto, Auto, Auto" ColumnDefinitions="20, 100, Auto, Auto">
|
||||
<TextBlock FontSize="20" Grid.ColumnSpan="4">Logins</TextBlock>
|
||||
|
||||
<Image Grid.Row="1" Grid.Column="0" Width="16" Height="16" Margin="4"
|
||||
Source="../Assets/Downloaders/nexus.ico" HorizontalAlignment="Right" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="Nexus" VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left" />
|
||||
<Button Grid.Row="1" Grid.Column="2" x:Name="NexusLogIn">Log In</Button>
|
||||
<Button Grid.Row="1" Grid.Column="3" x:Name="NexusLogOut">Log Out</Button>
|
||||
|
||||
<Image Grid.Row="2" Grid.Column="0" Width="16" Height="16" Margin="4"
|
||||
Source="../Assets/Downloaders/loverslab.ico" HorizontalAlignment="Right" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" Text="Lovers Lab" VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left" />
|
||||
<Button Grid.Row="2" Grid.Column="2" x:Name="LoversLabLogIn">Log In</Button>
|
||||
<Button Grid.Row="2" Grid.Column="3" x:Name="LoversLabLogOut">Log Out</Button>
|
||||
|
||||
<Image Grid.Row="3" Grid.Column="0" Width="16" Height="16" Margin="4"
|
||||
Source="../Assets/Downloaders/vectorplexus.ico" HorizontalAlignment="Right" />
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="Vector Plexus" VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left" />
|
||||
<Button Grid.Row="3" Grid.Column="2" x:Name="VectorPlexusLogIn">Log In</Button>
|
||||
<Button Grid.Row="3" Grid.Column="3" x:Name="VectorPlexusLogOut">Log Out</Button>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border x:Name="ResourcesBorder" Margin="5" BorderThickness="1" Classes="ResourceSettings StandardBorder">
|
||||
<Grid RowDefinitions="Auto, Auto, Auto, Auto" ColumnDefinitions="140, 100, 140, 100">
|
||||
<TextBlock Grid.Row="0" Text="Resources" FontSize="20" Grid.ColumnSpan="4" Margin="4"></TextBlock>
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Name" FontWeight="Bold" Margin="4, 4"></TextBlock>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="Max Tasks" FontWeight="Bold" Margin="4, 4"></TextBlock>
|
||||
<TextBlock Grid.Row="1" Grid.Column="2" Text="Max Throughput" FontWeight="Bold" Margin="4, 4"></TextBlock>
|
||||
<TextBlock Grid.Row="1" Grid.Column="3" Text="Transferred" FontWeight="Bold" Margin="4, 4"></TextBlock>
|
||||
|
||||
<ItemsRepeater Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" x:Name="ResourcesList" Margin="0, 4">
|
||||
<ItemsRepeater.Layout>
|
||||
<StackLayout></StackLayout>
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:ResourceView></controls:ResourceView>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
|
||||
<Button Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" Margin="4" Click="SaveSettingsAndRestart">
|
||||
<TextBlock Text="Save Settings and Restart Wabbajack" HorizontalAlignment="Center" TextAlignment="Center"></TextBlock>
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</WrapPanel>
|
||||
</UserControl>
|
@ -1,43 +0,0 @@
|
||||
using System.Reactive.Disposables;
|
||||
using Avalonia.Interactivity;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.App.Views;
|
||||
using Wabbajack.Common;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public partial class SettingsView : ScreenBase<SettingsViewModel>
|
||||
{
|
||||
public SettingsView() : base("Settings")
|
||||
{
|
||||
InitializeComponent();
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.BindCommand(ViewModel, vm => vm.NexusLogin, view => view.NexusLogIn)
|
||||
.DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.NexusLogout, view => view.NexusLogOut)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.LoversLabLogin, view => view.LoversLabLogIn)
|
||||
.DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.LoversLabLogout, view => view.LoversLabLogOut)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.VectorPlexusLogin, view => view.VectorPlexusLogIn)
|
||||
.DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel, vm => vm.VectorPlexusLogout, view => view.VectorPlexusLogOut)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Resources, view => view.ResourcesList.Items)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void SaveSettingsAndRestart(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel!.SaveResourceSettingsAndRestart().FireAndForget();
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.App.Controls;
|
||||
using Wabbajack.App.Messages;
|
||||
using Wabbajack.App.Models;
|
||||
using Wabbajack.App.ViewModels;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.Paths.IO;
|
||||
using Wabbajack.RateLimiter;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
using Wabbajack.Services.OSIntegrated.TokenProviders;
|
||||
|
||||
namespace Wabbajack.App.Screens;
|
||||
|
||||
public class SettingsViewModel : ViewModelBase
|
||||
{
|
||||
private readonly Subject<AbsolutePath> _fileSystemEvents = new();
|
||||
private readonly ILogger<SettingsViewModel> _logger;
|
||||
public readonly IEnumerable<ResourceViewModel> Resources;
|
||||
private readonly ResourceSettingsManager _resourceSettingsManager;
|
||||
|
||||
public SettingsViewModel(ILogger<SettingsViewModel> logger, Configuration configuration,
|
||||
ResourceSettingsManager resourceSettingsManager,
|
||||
NexusApiTokenProvider nexusProvider, IEnumerable<IResource> resources, LoversLabTokenProvider llProvider, VectorPlexusTokenProvider vpProvider)
|
||||
{
|
||||
_resourceSettingsManager = resourceSettingsManager;
|
||||
_logger = logger;
|
||||
Resources = resources.Select(r => new ResourceViewModel(r))
|
||||
.OrderBy(o => o.Name)
|
||||
.ToArray();
|
||||
Activator = new ViewModelActivator();
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
foreach (var resource in Resources)
|
||||
{
|
||||
resource.Activator.Activate().DisposeWith(disposables);
|
||||
}
|
||||
|
||||
configuration.EncryptedDataLocation.CreateDirectory();
|
||||
Watcher = new FileSystemWatcher(configuration.EncryptedDataLocation.ToString());
|
||||
Watcher.DisposeWith(disposables);
|
||||
Watcher.Created += Pulse;
|
||||
Watcher.Deleted += Pulse;
|
||||
Watcher.Renamed += Pulse;
|
||||
Watcher.Changed += Pulse;
|
||||
|
||||
Watcher.EnableRaisingEvents = true;
|
||||
|
||||
var haveNexusToken = _fileSystemEvents
|
||||
.StartWith(AbsolutePath.Empty)
|
||||
.Select(_ => nexusProvider.HaveToken());
|
||||
|
||||
NexusLogin =
|
||||
ReactiveCommand.Create(() => { MessageBus.Current.SendMessage(new NavigateTo(typeof(NexusLoginViewModel))); },
|
||||
haveNexusToken.Select(x => !x));
|
||||
NexusLogout = ReactiveCommand.Create(nexusProvider.DeleteToken, haveNexusToken.Select(x => x));
|
||||
|
||||
var haveLLToken = _fileSystemEvents
|
||||
.StartWith(AbsolutePath.Empty)
|
||||
.Select(_ => llProvider.HaveToken());
|
||||
|
||||
LoversLabLogin =
|
||||
ReactiveCommand.Create(() => { MessageBus.Current.SendMessage(new NavigateTo(typeof(LoversLabOAuthLoginViewModel))); },
|
||||
haveLLToken.Select(x => !x));
|
||||
LoversLabLogout = ReactiveCommand.Create(llProvider.DeleteToken, haveLLToken.Select(x => x));
|
||||
|
||||
var haveVectorPlexusToken = _fileSystemEvents
|
||||
.StartWith(AbsolutePath.Empty)
|
||||
.Select(_ => vpProvider.HaveToken());
|
||||
|
||||
VectorPlexusLogin =
|
||||
ReactiveCommand.Create(() => { MessageBus.Current.SendMessage(new NavigateTo(typeof(VectorPlexusOAuthLoginViewModel))); },
|
||||
haveVectorPlexusToken.Select(x => !x));
|
||||
VectorPlexusLogout = ReactiveCommand.Create(vpProvider.DeleteToken, haveVectorPlexusToken.Select(x => x));
|
||||
});
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> NexusLogin { get; set; }
|
||||
public ReactiveCommand<Unit, Unit> NexusLogout { get; set; }
|
||||
|
||||
|
||||
public ReactiveCommand<Unit, Unit> LoversLabLogin { get; set; }
|
||||
public ReactiveCommand<Unit, Unit> LoversLabLogout { get; set; }
|
||||
|
||||
public ReactiveCommand<Unit, Unit> VectorPlexusLogin { get; set; }
|
||||
public ReactiveCommand<Unit, Unit> VectorPlexusLogout { get; set; }
|
||||
|
||||
public FileSystemWatcher Watcher { get; set; }
|
||||
|
||||
private void Pulse(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
_fileSystemEvents.OnNext(e.FullPath?.ToAbsolutePath() ?? default);
|
||||
}
|
||||
|
||||
public async Task SaveResourceSettingsAndRestart()
|
||||
{
|
||||
await _resourceSettingsManager.SaveSettings(Resources.ToDictionary(r => r.Name, r =>
|
||||
new ResourceSettingsManager.ResourceSetting()
|
||||
{
|
||||
MaxTasks = r.MaxTasks,
|
||||
MaxThroughput = r.MaxThroughput
|
||||
}));
|
||||
|
||||
var proc = new Process()
|
||||
{
|
||||
StartInfo = new ProcessStartInfo()
|
||||
{
|
||||
FileName = Process.GetCurrentProcess().MainModule!.FileName
|
||||
}
|
||||
};
|
||||
proc.Start();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:i="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
x:Class="Wabbajack.App.Views.StandardInstallationView">
|
||||
<Grid RowDefinitions="40, 5, 5, *, 40" Margin="4">
|
||||
<TextBlock Grid.Row="0" x:Name="StatusText" FontSize="20" FontWeight="Bold">[20/30] Installing Files</TextBlock>
|
||||
<ProgressBar Grid.Row="1" x:Name="StepsProgress" Maximum="1000" Value="0" />
|
||||
<ProgressBar Grid.Row="2" x:Name="StepProgress" Maximum="10000" Value="0" />
|
||||
<Viewbox Grid.Row="3" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Stretch="Uniform">
|
||||
<Image x:Name="SlideImage" />
|
||||
</Viewbox>
|
||||
<Border Grid.Row="3" VerticalAlignment="Bottom" HorizontalAlignment="Center" CornerRadius="4" Background="#111111" Opacity="25">
|
||||
<StackPanel Orientation="Vertical" Margin="4">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock FontSize="24" x:Name="ModTitle" FontWeight="Bold"></TextBlock>
|
||||
<TextBlock FontSize="12" FontStyle="Italic" Text="by" Margin="8, 0, 4, 4" VerticalAlignment="Bottom"></TextBlock>
|
||||
<TextBlock FontSize="12" x:Name="AuthorName" FontStyle="Italic" VerticalAlignment="Bottom" Margin="0, 0, 4, 4" ></TextBlock>
|
||||
</StackPanel>
|
||||
<TextBlock FontSize="12" x:Name="ModDescription" TextWrapping="Wrap"></TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Grid Grid.Row="4" HorizontalAlignment="Center" ColumnDefinitions="40, 40, 40, 40">
|
||||
<Button Grid.Column="0" x:Name="PrevSlide">
|
||||
<i:MaterialIcon Kind="ArrowLeft" />
|
||||
</Button>
|
||||
<Button Grid.Column="1" x:Name="PauseSlides">
|
||||
<i:MaterialIcon Kind="Pause" />
|
||||
</Button>
|
||||
<Button Grid.Column="2" x:Name="PlaySlides">
|
||||
<i:MaterialIcon Kind="PlayArrow" />
|
||||
</Button>
|
||||
<Button Grid.Column="3" x:Name="NextSlide">
|
||||
<i:MaterialIcon Kind="ArrowRight" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,54 +0,0 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using Avalonia.Threading;
|
||||
using ReactiveUI;
|
||||
using Wabbajack.App.Extensions;
|
||||
using Wabbajack.App.ViewModels;
|
||||
|
||||
namespace Wabbajack.App.Views;
|
||||
|
||||
public partial class StandardInstallationView : ScreenBase<StandardInstallationViewModel>
|
||||
{
|
||||
public StandardInstallationView() : base("Installing")
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.OneWayBind(ViewModel, vm => vm.Slide.Image, view => view.SlideImage.Source)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.NextCommand, view => view.NextSlide)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.PrevCommand, view => view.PrevSlide)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.PauseCommand, view => view.PauseSlides)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.BindCommand(ViewModel, vm => vm.PlayCommand, view => view.PlaySlides)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.StatusText, view => view.StatusText.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.StepsProgress, view => view.StepsProgress.Value, p => p.Value * 1000)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.StepProgress, p => p.StepProgress.Value, p => p.Value * 10000)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Slide.MetaState.Name, view => view.ModTitle.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Slide.MetaState.Description, view => view.ModDescription.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.OneWayBind(ViewModel, vm => vm.Slide.MetaState.Author, view => view.AuthorName.Text)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
});
|
||||
}
|
||||
}
|
@ -1,244 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Threading;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.Messages;
|
||||
using Wabbajack.App.Models;
|
||||
using Wabbajack.App.Screens;
|
||||
using Wabbajack.App.Utilities;
|
||||
using Wabbajack.App.ViewModels.SubViewModels;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Downloaders.GameFile;
|
||||
using Wabbajack.Downloaders.Interfaces;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.DTOs.DownloadStates;
|
||||
using Wabbajack.DTOs.JsonConverters;
|
||||
using Wabbajack.DTOs.SavedSettings;
|
||||
using Wabbajack.Installer;
|
||||
using Wabbajack.Paths.IO;
|
||||
using Wabbajack.RateLimiter;
|
||||
|
||||
namespace Wabbajack.App.ViewModels;
|
||||
|
||||
public class StandardInstallationViewModel : ViewModelBase
|
||||
{
|
||||
private readonly DTOSerializer _dtos;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly InstallationStateManager _installStateManager;
|
||||
private readonly GameLocator _locator;
|
||||
private readonly ILogger<StandardInstallationViewModel> _logger;
|
||||
private readonly IServiceProvider _provider;
|
||||
private InstallerConfiguration _config;
|
||||
private int _currentSlideIndex;
|
||||
private StandardInstaller _installer;
|
||||
private IServiceScope _scope;
|
||||
private SlideViewModel[] _slides = Array.Empty<SlideViewModel>();
|
||||
private Timer _slideTimer;
|
||||
private Timer _updateTimer;
|
||||
|
||||
public StandardInstallationViewModel(ILogger<StandardInstallationViewModel> logger, IServiceProvider provider,
|
||||
GameLocator locator, DTOSerializer dtos,
|
||||
HttpClient httpClient, InstallationStateManager manager)
|
||||
{
|
||||
_provider = provider;
|
||||
_locator = locator;
|
||||
_logger = logger;
|
||||
_dtos = dtos;
|
||||
_httpClient = httpClient;
|
||||
_installStateManager = manager;
|
||||
Activator = new ViewModelActivator();
|
||||
|
||||
MessageBus.Current.Listen<StartInstallation>()
|
||||
.Subscribe(Receive)
|
||||
.DisposeWith(VMDisposables);
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
_updateTimer = new Timer(UpdateStatus, null, TimeSpan.FromMilliseconds(1), TimeSpan.FromMilliseconds(100));
|
||||
_updateTimer.DisposeWith(disposables);
|
||||
|
||||
_slideTimer = new Timer(_ =>
|
||||
{
|
||||
if (IsPlaying) NextSlide(1);
|
||||
}, null, TimeSpan.FromSeconds(0.1), TimeSpan.FromSeconds(5));
|
||||
|
||||
_currentSlideIndex = 0;
|
||||
_slideTimer.DisposeWith(disposables);
|
||||
|
||||
NextCommand = ReactiveCommand.Create(() => NextSlide(1))
|
||||
.DisposeWith(disposables);
|
||||
PrevCommand = ReactiveCommand.Create(() => NextSlide(-1))
|
||||
.DisposeWith(disposables);
|
||||
PauseCommand = ReactiveCommand.Create(() => IsPlaying = false,
|
||||
this.ObservableForProperty(vm => vm.IsPlaying, skipInitial: false)
|
||||
.Select(v => v.Value))
|
||||
.DisposeWith(disposables);
|
||||
|
||||
PlayCommand = ReactiveCommand.Create(() => IsPlaying = true,
|
||||
this.ObservableForProperty(vm => vm.IsPlaying, skipInitial: false)
|
||||
.Select(v => !v.Value))
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
[Reactive] public SlideViewModel Slide { get; set; }
|
||||
|
||||
[Reactive] public ReactiveCommand<Unit, Unit> NextCommand { get; set; }
|
||||
|
||||
[Reactive] public ReactiveCommand<Unit, Unit> PrevCommand { get; set; }
|
||||
|
||||
[Reactive] public ReactiveCommand<Unit, bool> PauseCommand { get; set; }
|
||||
|
||||
[Reactive] public ReactiveCommand<Unit, bool> PlayCommand { get; set; }
|
||||
|
||||
[Reactive] public bool IsPlaying { get; set; } = true;
|
||||
|
||||
[Reactive] public string StatusText { get; set; } = "";
|
||||
[Reactive] public Percent StepsProgress { get; set; } = Percent.Zero;
|
||||
[Reactive] public Percent StepProgress { get; set; } = Percent.Zero;
|
||||
|
||||
// Not Reactive, so we don't end up spamming the UI threads with events
|
||||
public StatusUpdate _latestStatus = new("", Percent.Zero, Percent.Zero);
|
||||
|
||||
public void Receive(StartInstallation msg)
|
||||
{
|
||||
Install(msg).FireAndForget();
|
||||
}
|
||||
|
||||
private void UpdateStatus(object? state)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
StepsProgress = _latestStatus.StepsProgress;
|
||||
StepProgress = _latestStatus.StepProgress;
|
||||
StatusText = _latestStatus.StatusText;
|
||||
}, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
private void NextSlide(int direction)
|
||||
{
|
||||
if (_slides.Length == 0) return;
|
||||
_currentSlideIndex = InSlideRange(_currentSlideIndex + direction);
|
||||
|
||||
var thisSlide = _slides[_currentSlideIndex];
|
||||
|
||||
if (thisSlide.Image == null)
|
||||
thisSlide.PreCache(_httpClient).FireAndForget();
|
||||
|
||||
// Cache the next image
|
||||
_slides[InSlideRange(_currentSlideIndex + 1)].PreCache(_httpClient).FireAndForget();
|
||||
|
||||
var prevSlide = _slides[InSlideRange(_currentSlideIndex - 2)];
|
||||
//if (prevSlide.Image != null)
|
||||
// prevSlide.Image = null;
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() => { Slide = thisSlide; });
|
||||
}
|
||||
|
||||
private int InSlideRange(int i)
|
||||
{
|
||||
while (!(i >= 0 && i <= _slides.Length))
|
||||
{
|
||||
if (i >= _slides.Length) i -= _slides.Length;
|
||||
if (i < 0) i += _slides.Length;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
private async Task Install(StartInstallation msg)
|
||||
{
|
||||
_scope = _provider.CreateScope();
|
||||
_config = _provider.GetService<InstallerConfiguration>()!;
|
||||
_config.Downloads = msg.Download;
|
||||
_config.Install = msg.Install;
|
||||
_config.ModlistArchive = msg.ModListPath;
|
||||
_config.Metadata = msg.Metadata;
|
||||
|
||||
_logger.LogInformation("Loading ModList Data");
|
||||
_config.ModList = await StandardInstaller.LoadFromFile(_dtos, msg.ModListPath);
|
||||
_config.Game = _config.ModList.GameType;
|
||||
|
||||
_slides = _config.ModList.Archives.Select(a => a.State).OfType<IMetaState>()
|
||||
.Select(m => new SlideViewModel {MetaState = m})
|
||||
.Where(s => !s.MetaState.IsNSFW)
|
||||
.Shuffle()
|
||||
.ToArray();
|
||||
|
||||
_slides[1].PreCache(_httpClient).FireAndForget();
|
||||
Slide = _slides[1];
|
||||
|
||||
if (_config.GameFolder == default)
|
||||
{
|
||||
if (!_locator.TryFindLocation(_config.Game, out var found))
|
||||
{
|
||||
_logger.LogCritical("Game {game} is not installed on this system",
|
||||
_config.Game.MetaData().HumanFriendlyGameName);
|
||||
throw new Exception("Game not found");
|
||||
}
|
||||
|
||||
_config.GameFolder = found;
|
||||
}
|
||||
|
||||
_installer = _provider.GetService<StandardInstaller>()!;
|
||||
|
||||
_installer.OnStatusUpdate = update => _latestStatus = update;
|
||||
|
||||
_logger.LogInformation("Installer created, starting the installation process");
|
||||
try
|
||||
{
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_config.ModList.Readme))
|
||||
OSUtil.OpenWebsite(new Uri(_config.ModList.Readme));
|
||||
|
||||
var result = await Task.Run(async () => await _installer.Begin(CancellationToken.None));
|
||||
if (!result) throw new Exception("Installation failed");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_config.ModList.Readme))
|
||||
OSUtil.OpenWebsite(new Uri(_config.ModList.Readme));
|
||||
|
||||
if (result) await SaveConfigAndContinue(_config);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "During installation");
|
||||
ErrorPageViewModel.Display("During installation", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task SaveConfigAndContinue(InstallerConfiguration config)
|
||||
{
|
||||
var path = config.Install.Combine("modlist-image.png");
|
||||
{
|
||||
var image = await ModListUtilities.GetModListImageStream(config.ModlistArchive);
|
||||
await using var os = path.Open(FileMode.Create, FileAccess.Write);
|
||||
await image.CopyToAsync(os);
|
||||
}
|
||||
|
||||
|
||||
await _installStateManager.SetLastState(new InstallationConfigurationSetting
|
||||
{
|
||||
Downloads = config.Downloads,
|
||||
Install = config.Install,
|
||||
Metadata = config.Metadata,
|
||||
ModList = config.ModlistArchive,
|
||||
Image = path,
|
||||
StrippedModListData = config.ModList.Strip()
|
||||
});
|
||||
|
||||
MessageBus.Current.SendMessage(new ConfigureLauncher(config.Install));
|
||||
MessageBus.Current.SendMessage(new NavigateTo(typeof(LauncherViewModel)));
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Threading;
|
||||
using CefNet;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Wabbajack.App.Controls;
|
||||
using Wabbajack.App.Interfaces;
|
||||
using Wabbajack.App.Messages;
|
||||
using Wabbajack.App.Models;
|
||||
using Wabbajack.App.Screens;
|
||||
using Wabbajack.App.Services;
|
||||
using Wabbajack.App.Utilities;
|
||||
using Wabbajack.App.ViewModels;
|
||||
using Wabbajack.App.Views;
|
||||
using Wabbajack.Downloaders.Interfaces;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.DTOs.DownloadStates;
|
||||
using Wabbajack.DTOs.JsonConverters;
|
||||
using Wabbajack.Paths.IO;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
|
||||
namespace Wabbajack.App;
|
||||
|
||||
public static class ServiceExtensions
|
||||
{
|
||||
private const int messagePumpDelay = 10;
|
||||
|
||||
|
||||
private static CefAppImpl app;
|
||||
private static Timer messagePump;
|
||||
|
||||
public static IServiceCollection AddAppServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddAllSingleton<ILoggerProvider, LoggerProvider>();
|
||||
services.AddSingleton<MainWindow>();
|
||||
services.AddSingleton<BrowseViewModel>();
|
||||
services.AddTransient<BrowseItemViewModel>();
|
||||
services.AddTransient<LogViewModel>();
|
||||
|
||||
services.AddTransient<InstalledListViewModel>();
|
||||
|
||||
services.AddDTOConverters();
|
||||
services.AddDTOSerializer();
|
||||
services.AddSingleton<ModeSelectionViewModel>();
|
||||
services.AddTransient<FileSelectionBoxViewModel>();
|
||||
services.AddSingleton<IScreenView, ErrorPageView>();
|
||||
services.AddSingleton<IScreenView, LogScreenView>();
|
||||
services.AddSingleton<IScreenView, ModeSelectionView>();
|
||||
services.AddSingleton<IScreenView, InstallConfigurationView>();
|
||||
services.AddSingleton<IScreenView, CompilerConfigurationView>();
|
||||
services.AddSingleton<IScreenView, StandardInstallationView>();
|
||||
services.AddSingleton<IScreenView, CompilationView>();
|
||||
services.AddSingleton<IScreenView, SettingsView>();
|
||||
services.AddSingleton<IScreenView, BrowseView>();
|
||||
services.AddSingleton<IScreenView, LauncherView>();
|
||||
services.AddSingleton<IScreenView, PlaySelectView>();
|
||||
services.AddSingleton<ImageCache>();
|
||||
|
||||
services.AddSingleton<InstallationStateManager>();
|
||||
services.AddSingleton<HttpClient>();
|
||||
|
||||
services.AddSingleton<LogScreenViewModel>();
|
||||
services.AddSingleton<PlaySelectViewModel>();
|
||||
services.AddSingleton<ErrorPageViewModel>();
|
||||
services.AddSingleton<StandardInstallationViewModel>();
|
||||
services.AddSingleton<InstallConfigurationViewModel>();
|
||||
services.AddSingleton<CompilerConfigurationViewModel>();
|
||||
services.AddSingleton<MainWindowViewModel>();
|
||||
services.AddSingleton<SettingsViewModel>();
|
||||
services.AddSingleton<NexusLoginViewModel>();
|
||||
services.AddSingleton<LoversLabOAuthLoginViewModel>();
|
||||
services.AddSingleton<VectorPlexusOAuthLoginViewModel>();
|
||||
services.AddSingleton<CompilationViewModel>();
|
||||
services.AddSingleton<LauncherViewModel>();
|
||||
|
||||
// Services
|
||||
services.AddAllSingleton<IDownloader, IDownloader<Manual>, ManualDownloader>();
|
||||
|
||||
var resources = KnownFolders.EntryPoint;
|
||||
services.AddSingleton(s => new CefSettings
|
||||
{
|
||||
NoSandbox = true,
|
||||
PersistSessionCookies = true,
|
||||
MultiThreadedMessageLoop = false,
|
||||
WindowlessRenderingEnabled = true,
|
||||
ExternalMessagePump = true,
|
||||
LocalesDirPath = resources.Combine("locales").ToString(),
|
||||
ResourcesDirPath = resources.ToString(),
|
||||
UserAgent = "",
|
||||
CachePath = KnownFolders.WabbajackAppLocal.Combine("cef_cache").ToString()
|
||||
});
|
||||
|
||||
services.AddSingleton(s =>
|
||||
{
|
||||
App.FrameworkInitialized += App_FrameworkInitialized;
|
||||
|
||||
var app = new CefAppImpl();
|
||||
app.ScheduleMessagePumpWorkCallback = OnScheduleMessagePumpWork;
|
||||
|
||||
app.CefProcessMessageReceived += App_CefProcessMessageReceived;
|
||||
app.Initialize(resources.ToString(), s.GetService<CefSettings>());
|
||||
|
||||
|
||||
return app;
|
||||
});
|
||||
|
||||
services.AddOSIntegrated();
|
||||
return services;
|
||||
}
|
||||
|
||||
private static async void OnScheduleMessagePumpWork(long delayMs)
|
||||
{
|
||||
await Task.Delay((int) delayMs);
|
||||
Dispatcher.UIThread.Post(CefApi.DoMessageLoopWork);
|
||||
}
|
||||
|
||||
private static void App_CefProcessMessageReceived(object? sender, CefProcessMessageReceivedEventArgs e)
|
||||
{
|
||||
var msg = e.Name;
|
||||
}
|
||||
|
||||
private static void App_FrameworkInitialized(object? sender, EventArgs e)
|
||||
{
|
||||
if (CefNetApplication.Instance.UsesExternalMessageLoop)
|
||||
messagePump = new Timer(_ => Dispatcher.UIThread.Post(CefApi.DoMessageLoopWork), null, messagePumpDelay,
|
||||
messagePumpDelay);
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Downloaders.Interfaces;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.DTOs.DownloadStates;
|
||||
using Wabbajack.DTOs.Validation;
|
||||
using Wabbajack.Hashing.xxHash64;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.RateLimiter;
|
||||
|
||||
namespace Wabbajack.App.Services;
|
||||
|
||||
public class ManualDownloader : ADownloader<Manual>
|
||||
{
|
||||
public override Priority Priority { get; }
|
||||
|
||||
public override Task<Hash> Download(Archive archive, Manual state, AbsolutePath destination, IJob job,
|
||||
CancellationToken token)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Task<bool> Prepare()
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public override bool IsAllowed(ServerAllowList allowList, IDownloadState state)
|
||||
{
|
||||
var manual = (Manual) state;
|
||||
return allowList.AllowedPrefixes.Any(p => manual.Url.ToString().StartsWith(p));
|
||||
}
|
||||
|
||||
public override IDownloadState? Resolve(IReadOnlyDictionary<string, string> iniData)
|
||||
{
|
||||
if (iniData.TryGetValue("manualURL", out var manual)) return new Manual {Url = new Uri(manual)};
|
||||
return null;
|
||||
}
|
||||
|
||||
public override Task<bool> Verify(Archive archive, Manual archiveState, IJob job, CancellationToken token)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public override IEnumerable<string> MetaIni(Archive a, Manual state)
|
||||
{
|
||||
return new[] {$"manualURL={state.Url}"};
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using CefNet;
|
||||
|
||||
namespace Wabbajack.App.Utilities;
|
||||
|
||||
internal class CefAppImpl : CefNetApplication
|
||||
{
|
||||
public Action<long> ScheduleMessagePumpWorkCallback { get; set; }
|
||||
|
||||
protected override void OnBeforeCommandLineProcessing(string processType, CefCommandLine commandLine)
|
||||
{
|
||||
base.OnBeforeCommandLineProcessing(processType, commandLine);
|
||||
|
||||
Console.WriteLine("ChromiumWebBrowser_OnBeforeCommandLineProcessing");
|
||||
Console.WriteLine(commandLine.CommandLineString);
|
||||
|
||||
//commandLine.AppendSwitchWithValue("proxy-server", "127.0.0.1:8888");
|
||||
|
||||
|
||||
commandLine.AppendSwitchWithValue("remote-debugging-port", "9222");
|
||||
|
||||
//enable-devtools-experiments
|
||||
commandLine.AppendSwitch("enable-devtools-experiments");
|
||||
|
||||
//e.CommandLine.AppendSwitchWithValue("user-agent", "Mozilla/5.0 (Windows 10.0) WebKa/" + DateTime.UtcNow.Ticks);
|
||||
|
||||
//("force-device-scale-factor", "1");
|
||||
|
||||
//commandLine.AppendSwitch("disable-gpu");
|
||||
//commandLine.AppendSwitch("disable-gpu-compositing");
|
||||
//commandLine.AppendSwitch("disable-gpu-vsync");
|
||||
|
||||
commandLine.AppendSwitch("enable-begin-frame-scheduling");
|
||||
commandLine.AppendSwitch("enable-media-stream");
|
||||
|
||||
commandLine.AppendSwitchWithValue("enable-blink-features", "CSSPseudoHas");
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
commandLine.AppendSwitch("no-zygote");
|
||||
commandLine.AppendSwitch("no-sandbox");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnContextCreated(CefBrowser browser, CefFrame frame, CefV8Context context)
|
||||
{
|
||||
base.OnContextCreated(browser, frame, context);
|
||||
frame.ExecuteJavaScript(@"
|
||||
{
|
||||
const newProto = navigator.__proto__;
|
||||
delete newProto.webdriver;
|
||||
navigator.__proto__ = newProto;
|
||||
}", frame.Url, 0);
|
||||
}
|
||||
|
||||
protected override void OnCefProcessMessageReceived(CefProcessMessageReceivedEventArgs e)
|
||||
{
|
||||
base.OnCefProcessMessageReceived(e);
|
||||
}
|
||||
|
||||
protected override CefRenderProcessHandler GetRenderProcessHandler()
|
||||
{
|
||||
return base.GetRenderProcessHandler();
|
||||
}
|
||||
|
||||
protected override void OnScheduleMessagePumpWork(long delayMs)
|
||||
{
|
||||
ScheduleMessagePumpWorkCallback(delayMs);
|
||||
}
|
||||
}
|
@ -1,151 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using DynamicData;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.Paths.IO;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
|
||||
namespace Wabbajack.App.Utilities;
|
||||
|
||||
public class LoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly RelativePath _appName;
|
||||
private readonly Configuration _configuration;
|
||||
private readonly CompositeDisposable _disposables;
|
||||
private readonly Stream _logFile;
|
||||
private readonly StreamWriter _logStream;
|
||||
|
||||
public readonly ReadOnlyObservableCollection<ILogMessage> _messagesFiltered;
|
||||
private readonly DateTime _startupTime;
|
||||
|
||||
private long _messageId;
|
||||
private readonly SourceCache<ILogMessage, long> _messageLog = new(m => m.MessageId);
|
||||
private readonly Subject<ILogMessage> _messages = new();
|
||||
|
||||
public LoggerProvider(Configuration configuration)
|
||||
{
|
||||
_startupTime = DateTime.UtcNow;
|
||||
_configuration = configuration;
|
||||
_configuration.LogLocation.CreateDirectory();
|
||||
|
||||
_disposables = new CompositeDisposable();
|
||||
|
||||
Messages.Subscribe(m => _messageLog.AddOrUpdate(m))
|
||||
.DisposeWith(_disposables);
|
||||
|
||||
Messages.Subscribe(m => LogToFile(m))
|
||||
.DisposeWith(_disposables);
|
||||
|
||||
_messageLog.Connect()
|
||||
.Bind(out _messagesFiltered)
|
||||
.Subscribe()
|
||||
.DisposeWith(_disposables);
|
||||
|
||||
_messages.DisposeWith(_disposables);
|
||||
|
||||
_appName = typeof(LoggerProvider).Assembly.Location.ToAbsolutePath().FileName;
|
||||
LogPath = _configuration.LogLocation.Combine($"{_appName}.current.log");
|
||||
_logFile = LogPath.Open(FileMode.Append, FileAccess.Write);
|
||||
_logFile.DisposeWith(_disposables);
|
||||
|
||||
_logStream = new StreamWriter(_logFile, Encoding.UTF8);
|
||||
}
|
||||
|
||||
public IObservable<ILogMessage> Messages => _messages;
|
||||
public AbsolutePath LogPath { get; }
|
||||
public ReadOnlyObservableCollection<ILogMessage> MessageLog => _messagesFiltered;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disposables.Dispose();
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new Logger(this, categoryName);
|
||||
}
|
||||
|
||||
private void LogToFile(ILogMessage logMessage)
|
||||
{
|
||||
var line = $"[{logMessage.TimeStamp - _startupTime}] {logMessage.LongMessage}";
|
||||
lock (_logStream)
|
||||
{
|
||||
_logStream.Write(line);
|
||||
_logStream.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
private long NextMessageId()
|
||||
{
|
||||
return Interlocked.Increment(ref _messageId);
|
||||
}
|
||||
|
||||
public class Logger : ILogger
|
||||
{
|
||||
private readonly string _categoryName;
|
||||
private readonly LoggerProvider _provider;
|
||||
private ImmutableList<object> Scopes = ImmutableList<object>.Empty;
|
||||
|
||||
public Logger(LoggerProvider provider, string categoryName)
|
||||
{
|
||||
_categoryName = categoryName;
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
{
|
||||
_provider._messages.OnNext(new LogMessage<TState>(DateTime.UtcNow, _provider.NextMessageId(), logLevel,
|
||||
eventId, state, exception, formatter));
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
Scopes = Scopes.Add(state);
|
||||
return Disposable.Create(() => Scopes = Scopes.Remove(state));
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILogMessage
|
||||
{
|
||||
long MessageId { get; }
|
||||
|
||||
string ShortMessage { get; }
|
||||
DateTime TimeStamp { get; }
|
||||
string LongMessage { get; }
|
||||
}
|
||||
|
||||
private record LogMessage<TState>(DateTime TimeStamp, long MessageId, LogLevel LogLevel, EventId EventId,
|
||||
TState State, Exception? Exception, Func<TState, Exception?, string> Formatter) : ILogMessage
|
||||
{
|
||||
public string ShortMessage => Formatter(State, Exception);
|
||||
|
||||
public string LongMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(ShortMessage);
|
||||
if (Exception != null)
|
||||
{
|
||||
sb.Append("Exception: ");
|
||||
sb.Append(Exception);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Threading.Tasks;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.Paths.IO;
|
||||
|
||||
namespace Wabbajack.App.Utilities;
|
||||
|
||||
public class ModListUtilities
|
||||
{
|
||||
public static async Task<MemoryStream> GetModListImageStream(AbsolutePath modList)
|
||||
{
|
||||
await using var fs = modList.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using var ar = new ZipArchive(fs, ZipArchiveMode.Read);
|
||||
var entry = ar.GetEntry("modlist-image.png");
|
||||
await using var stream = entry!.Open();
|
||||
return new MemoryStream(await stream.ReadAllAsync());
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.Paths.IO;
|
||||
|
||||
namespace Wabbajack.App.Utilities;
|
||||
|
||||
public static class OSUtil
|
||||
{
|
||||
public static void OpenWebsite(Uri uri)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
var helper = new ProcessHelper()
|
||||
{
|
||||
Path = "cmd.exe".ToRelativePath().RelativeTo(KnownFolders.WindowsSystem32),
|
||||
Arguments = new[] {"/C", $"rundll32 url.dll,FileProtocolHandler {uri}"}
|
||||
};
|
||||
helper.Start().FireAndForget();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void OpenFolder(AbsolutePath path)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
var helper = new ProcessHelper()
|
||||
{
|
||||
Path = "explorer.exe".ToRelativePath().RelativeTo(KnownFolders.Windows),
|
||||
Arguments = new object[] {path}
|
||||
};
|
||||
helper.Start().FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Wabbajack.App;
|
||||
|
||||
public static class Utils
|
||||
{
|
||||
public static void OpenWebsiteInExternalBrowser(Uri uri)
|
||||
{
|
||||
Process.Start(uri.ToString());
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Wabbajack.App.ViewModels;
|
||||
|
||||
namespace Wabbajack.App;
|
||||
|
||||
public class ViewLocator : IDataTemplate
|
||||
{
|
||||
public bool SupportsRecycling => false;
|
||||
|
||||
public IControl Build(object data)
|
||||
{
|
||||
var name = data.GetType().FullName!.Replace("ViewModel", "View");
|
||||
var type = Type.GetType(name);
|
||||
|
||||
if (type != null)
|
||||
return (Control) Activator.CreateInstance(type)!;
|
||||
return new TextBlock {Text = "Not Found: " + name};
|
||||
}
|
||||
|
||||
public bool Match(object data)
|
||||
{
|
||||
return data is ViewModelBase;
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CefNet.Avalonia;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using Wabbajack.App.Messages;
|
||||
|
||||
namespace Wabbajack.App.ViewModels;
|
||||
|
||||
public abstract class GuidedWebViewModel : ViewModelBase
|
||||
{
|
||||
protected ILogger _logger;
|
||||
|
||||
public GuidedWebViewModel(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
Activator = new ViewModelActivator();
|
||||
|
||||
this.WhenActivated(disposables => { Disposable.Empty.DisposeWith(disposables); });
|
||||
}
|
||||
|
||||
[Reactive] public string Instructions { get; set; }
|
||||
|
||||
public WebView Browser { get; set; }
|
||||
|
||||
public abstract Task Run(CancellationToken token);
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls.Mixins;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using ReactiveUI.Validation.Extensions;
|
||||
using Wabbajack.App.Extensions;
|
||||
using Wabbajack.App.Messages;
|
||||
using Wabbajack.App.Models;
|
||||
using Wabbajack.App.Utilities;
|
||||
using Wabbajack.Common;
|
||||
using Wabbajack.DTOs;
|
||||
using Wabbajack.DTOs.JsonConverters;
|
||||
using Wabbajack.DTOs.SavedSettings;
|
||||
using Wabbajack.Installer;
|
||||
using Wabbajack.Paths;
|
||||
using Wabbajack.Paths.IO;
|
||||
using Wabbajack.Services.OSIntegrated;
|
||||
|
||||
namespace Wabbajack.App.ViewModels;
|
||||
|
||||
public class InstallConfigurationViewModel : ViewModelBase, IActivatableViewModel
|
||||
{
|
||||
private readonly DTOSerializer _dtos;
|
||||
private readonly InstallationStateManager _stateManager;
|
||||
private readonly SettingsManager _settingsManager;
|
||||
|
||||
public InstallConfigurationViewModel(DTOSerializer dtos, InstallationStateManager stateManager, SettingsManager settingsManager)
|
||||
{
|
||||
_stateManager = stateManager;
|
||||
_settingsManager = settingsManager;
|
||||
|
||||
_dtos = dtos;
|
||||
Activator = new ViewModelActivator();
|
||||
|
||||
MessageBus.Current.Listen<StartInstallConfiguration>()
|
||||
.Subscribe(Receive)
|
||||
.DisposeWith(VMDisposables);
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.ValidationRule(x => x.ModListPath, p => p.FileExists(), "Wabbajack file must exist");
|
||||
this.ValidationRule(x => x.Install, p => p.DirectoryExists(), "Install folder file must exist");
|
||||
this.ValidationRule(x => x.Download, p => p != default, "Download folder must be set");
|
||||
|
||||
BeginCommand = ReactiveCommand.Create(() => { StartInstall().FireAndForget(); }, this.IsValid());
|
||||
|
||||
|
||||
this.WhenAnyValue(t => t.ModListPath)
|
||||
.Where(t => t != default)
|
||||
.SelectMany(async x => await LoadModList(x))
|
||||
.OnUIThread()
|
||||
.BindTo(this, t => t.ModList)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
this.WhenAnyValue(t => t.ModListPath)
|
||||
.Where(t => t != default)
|
||||
.SelectMany(async x => await LoadModListImage(x))
|
||||
.OnUIThread()
|
||||
.BindTo(this, t => t.ModListImage)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
var settings = this.WhenAnyValue(t => t.ModListPath)
|
||||
.SelectMany(async v => await _stateManager.Get(v))
|
||||
.OnUIThread()
|
||||
.Where(s => s != default && s.Install != default);
|
||||
|
||||
settings.Select(s => s!.Install)
|
||||
.BindTo(this, vm => vm.Install)
|
||||
.DisposeWith(disposables);
|
||||
|
||||
settings.Select(s => s!.Downloads)
|
||||
.BindTo(this, vm => vm.Download)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
LoadSettings().FireAndForget();
|
||||
}
|
||||
|
||||
private async Task LoadSettings()
|
||||
{
|
||||
var path = await _settingsManager.Load<AbsolutePath>("last-install-path");
|
||||
if (path != default && path.FileExists())
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
ModListPath = path;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Reactive] public AbsolutePath ModListPath { get; set; }
|
||||
|
||||
[Reactive] public AbsolutePath Install { get; set; }
|
||||
|
||||
[Reactive] public AbsolutePath Download { get; set; }
|
||||
|
||||
[Reactive] public ModList? ModList { get; set; }
|
||||
|
||||
[Reactive] public IBitmap? ModListImage { get; set; }
|
||||
|
||||
[Reactive] public bool IsReady { get; set; }
|
||||
|
||||
[Reactive] public ReactiveCommand<Unit, Unit> BeginCommand { get; set; }
|
||||
|
||||
public ViewModelActivator Activator { get; }
|
||||
|
||||
private void Receive(StartInstallConfiguration val)
|
||||
{
|
||||
ModListPath = val.ModList;
|
||||
}
|
||||
|
||||
private async Task StartInstall()
|
||||
{
|
||||
ModlistMetadata? metadata = null;
|
||||
var metadataPath = ModListPath.WithExtension(Ext.MetaData);
|
||||
if (metadataPath.FileExists())
|
||||
metadata = _dtos.Deserialize<ModlistMetadata>(await metadataPath.ReadAllTextAsync());
|
||||
|
||||
await _stateManager.SetLastState(new InstallationConfigurationSetting
|
||||
{
|
||||
ModList = ModListPath,
|
||||
Downloads = Download,
|
||||
Install = Install,
|
||||
Metadata = metadata,
|
||||
StrippedModListData = ModList?.Strip()
|
||||
});
|
||||
|
||||
await _settingsManager.Save("last-install-path", ModListPath);
|
||||
|
||||
MessageBus.Current.SendMessage(new NavigateTo(typeof(StandardInstallationViewModel)));
|
||||
MessageBus.Current.SendMessage(new StartInstallation(ModListPath, Install, Download, metadata));
|
||||
}
|
||||
|
||||
private async Task<IBitmap> LoadModListImage(AbsolutePath path)
|
||||
{
|
||||
var img = new Bitmap(await ModListUtilities.GetModListImageStream(path));
|
||||
return img;
|
||||
}
|
||||
|
||||
private async Task<ModList> LoadModList(AbsolutePath modlist)
|
||||
{
|
||||
var definition = await StandardInstaller.LoadFromFile(_dtos, modlist);
|
||||
return definition;
|
||||
}
|
||||
}
|