Merge branch 'VM-split-and-styling'

This commit is contained in:
Justin Swanson 2019-11-02 15:55:14 -05:00
commit 8eaec1eabb
57 changed files with 2387 additions and 1249 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wabbajack
{
public static class TaskExt
{
public static async void FireAndForget(this Task task, Action<Exception> onException = null)
{
try
{
await task.ConfigureAwait(false);
}
catch (Exception ex)
when (onException != null)
{
onException(ex);
}
}
}
}

View File

@ -62,15 +62,36 @@ namespace Wabbajack.Lib
return null; return null;
} }
public static BitmapImage BitmapImageFromResource(string name) public static BitmapImage BitmapImageFromResource(string name) => BitmapImageFromStream(Utils.GetResourceStream(name));
public static BitmapImage BitmapImageFromStream(Stream stream)
{ {
var img = new BitmapImage(); var img = new BitmapImage();
img.BeginInit(); img.BeginInit();
img.StreamSource = Utils.GetResourceStream(name); img.StreamSource = stream;
img.EndInit(); img.EndInit();
return img; return img;
} }
public static bool TryGetBitmapImageFromFile(string path, out BitmapImage bitmapImage)
{
try
{
if (!File.Exists(path))
{
bitmapImage = default;
return false;
}
bitmapImage = new BitmapImage(new Uri(path, UriKind.RelativeOrAbsolute));
return true;
}
catch (Exception)
{
bitmapImage = default;
return false;
}
}
public static string OpenFileDialog(string filter) public static string OpenFileDialog(string filter)
{ {
OpenFileDialog ofd = new OpenFileDialog(); OpenFileDialog ofd = new OpenFileDialog();

View File

@ -160,6 +160,7 @@
<Compile Include="Downloaders\MEGADownloader.cs" /> <Compile Include="Downloaders\MEGADownloader.cs" />
<Compile Include="Downloaders\ModDBDownloader.cs" /> <Compile Include="Downloaders\ModDBDownloader.cs" />
<Compile Include="Downloaders\NexusDownloader.cs" /> <Compile Include="Downloaders\NexusDownloader.cs" />
<Compile Include="Extensions\TaskExt.cs" />
<Compile Include="Installer.cs" /> <Compile Include="Installer.cs" />
<Compile Include="ModListRegistry\ModListMetadata.cs" /> <Compile Include="ModListRegistry\ModListMetadata.cs" />
<Compile Include="NexusApi\Dtos.cs" /> <Compile Include="NexusApi\Dtos.cs" />

View File

@ -6,6 +6,9 @@
<Application.Resources> <Application.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Colors.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/BaseDark.xaml" />
<ResourceDictionary Source="Themes\Styles.xaml" /> <ResourceDictionary Source="Themes\Styles.xaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>

View File

@ -13,22 +13,14 @@ namespace Wabbajack
{ {
public App() public App()
{ {
/* // Wire any unhandled crashing exceptions to log before exiting
Utils.Log($"Wabbajack Build - {ThisAssembly.Git.Sha}"); AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
SetupHandlers();
var args = Environment.GetCommandLineArgs();
if (args.Length > 1)
{ {
Utils.SetLoggerFn(f => { }); // Don't do any special logging side effects
WorkQueue.Init((a, b, c) => { }, (a, b) => { }); Utils.SetLoggerFn((s) => { });
var updater = new CheckForUpdates(args[1]); Utils.Log("Uncaught error:");
if (updater.FindOutdatedMods()) Utils.Log(((Exception)e.ExceptionObject).ExceptionToString());
{ };
Environment.Exit(0);
}
Environment.Exit(1);
}*/
var appPath = Assembly.GetExecutingAssembly().Location; var appPath = Assembly.GetExecutingAssembly().Location;
if (!ExtensionManager.IsAssociated() || ExtensionManager.NeedsUpdating(appPath)) if (!ExtensionManager.IsAssociated() || ExtensionManager.NeedsUpdating(appPath))
@ -37,22 +29,11 @@ namespace Wabbajack
} }
string[] args = Environment.GetCommandLineArgs(); string[] args = Environment.GetCommandLineArgs();
StartupUri = new Uri("UI/ModeSelectionWindow.xaml", UriKind.Relative); StartupUri = new Uri("Views/ModeSelectionWindow.xaml", UriKind.Relative);
if (args.Length != 3) return; if (args.Length != 3) return;
if (!args[1].Contains("-i")) return; if (!args[1].Contains("-i")) return;
// modlists gets loaded using a shell command // modlists gets loaded using a shell command
StartupUri = new Uri("UI/MainWindow.xaml", UriKind.Relative); StartupUri = new Uri("Views/MainWindow.xaml", UriKind.Relative);
}
private void SetupHandlers()
{
AppDomain.CurrentDomain.UnhandledException += AppHandler;
}
private void AppHandler(object sender, UnhandledExceptionEventArgs e)
{
Utils.Log("Uncaught error:");
Utils.Log(((Exception)e.ExceptionObject).ExceptionToString());
} }
} }
} }

View File

@ -1,427 +0,0 @@
using Syroot.Windows.IO;
using System;
using ReactiveUI;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Reactive.Subjects;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Wabbajack.Common;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.NexusApi;
using Wabbajack.UI;
using DynamicData;
using DynamicData.Binding;
using System.Reactive;
using System.Text;
using Wabbajack.Lib;
namespace Wabbajack
{
public class AppState : ViewModel, IDataErrorInfo
{
public SlideShow Slideshow { get; }
public readonly string WabbajackVersion = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).FileVersion;
private string _mo2Folder;
public readonly BitmapImage _noneImage = UIUtils.BitmapImageFromResource("Wabbajack.UI.none.jpg");
private readonly Subject<CPUStatus> _statusSubject = new Subject<CPUStatus>();
public ObservableCollectionExtended<CPUStatus> Status { get; } = new ObservableCollectionExtended<CPUStatus>();
private ModList _ModList;
public ModList ModList { get => _ModList; private set => this.RaiseAndSetIfChanged(ref _ModList, value); }
private string _ModListPath;
public string ModListPath { get => _ModListPath; private set => this.RaiseAndSetIfChanged(ref _ModListPath, value); }
private RunMode _Mode;
public RunMode Mode { get => _Mode; private set => this.RaiseAndSetIfChanged(ref _Mode, value); }
private string _ModListName;
public string ModListName { get => _ModListName; set => this.RaiseAndSetIfChanged(ref _ModListName, value); }
private bool _UIReady;
public bool UIReady { get => _UIReady; set => this.RaiseAndSetIfChanged(ref _UIReady, value); }
private string _HTMLReport;
public string HTMLReport { get => _HTMLReport; set => this.RaiseAndSetIfChanged(ref _HTMLReport, value); }
private bool _Installing;
public bool Installing { get => _Installing; set => this.RaiseAndSetIfChanged(ref _Installing, value); }
// Command properties
public IReactiveCommand ChangePathCommand { get; }
public IReactiveCommand ChangeDownloadPathCommand { get; }
public IReactiveCommand BeginCommand { get; }
public IReactiveCommand ShowReportCommand { get; }
public IReactiveCommand OpenReadmeCommand { get; }
public IReactiveCommand OpenModListPropertiesCommand { get; }
public AppState(RunMode mode)
{
if (Path.GetDirectoryName(Assembly.GetEntryAssembly().Location.ToLower()) == KnownFolders.Downloads.Path.ToLower())
{
MessageBox.Show(
"Wabbajack is running inside your Downloads folder. This folder is often highly monitored by antivirus software and these can often " +
"conflict with the operations Wabbajack needs to perform. Please move this executable outside of your Downloads folder and then restart the app.",
"Cannot run inside Downloads",
MessageBoxButton.OK,
MessageBoxImage.Error);
Environment.Exit(1);
}
Mode = mode;
// Define commands
this.ChangePathCommand = ReactiveCommand.Create(ExecuteChangePath);
this.ChangeDownloadPathCommand = ReactiveCommand.Create(ExecuteChangeDownloadPath);
this.ShowReportCommand = ReactiveCommand.Create(ShowReport);
this.OpenModListPropertiesCommand = ReactiveCommand.Create(
execute: OpenModListProperties,
canExecute: this.WhenAny(x => x.UIReady)
.ObserveOnGuiThread());
this.OpenReadmeCommand = ReactiveCommand.Create(
execute: this.OpenReadmeWindow,
canExecute: this.WhenAny(x => x.ModList)
.Select(modList => !string.IsNullOrEmpty(modList?.Readme))
.ObserveOnGuiThread());
this.BeginCommand = ReactiveCommand.Create(
execute: this.ExecuteBegin,
canExecute: this.WhenAny(x => x.UIReady)
.ObserveOnGuiThread());
this.Slideshow = new SlideShow(this);
// Initialize work queue
WorkQueue.Init(
report_function: (id, msg, progress) => this._statusSubject.OnNext(new CPUStatus() { ID = id, Msg = msg, Progress = progress }),
report_queue_size: (max, current) => this.SetQueueSize(max, current));
// Compile progress updates and populate ObservableCollection
this._statusSubject
.ObserveOn(RxApp.TaskpoolScheduler)
.ToObservableChangeSet(x => x.ID)
.Batch(TimeSpan.FromMilliseconds(250))
.EnsureUniqueChanges()
.ObserveOn(RxApp.MainThreadScheduler)
.Sort(SortExpressionComparer<CPUStatus>.Ascending(s => s.ID), SortOptimisations.ComparesImmutableValuesOnly)
.Bind(this.Status)
.Subscribe()
.DisposeWith(this.CompositeDisposable);
}
public ObservableCollection<string> Log { get; } = new ObservableCollection<string>();
private string _Location;
public string Location { get => _Location; set => this.RaiseAndSetIfChanged(ref _Location, value); }
private string _LocationLabel;
public string LocationLabel { get => _LocationLabel; set => this.RaiseAndSetIfChanged(ref _LocationLabel, value); }
private string _DownloadLocation;
public string DownloadLocation { get => _DownloadLocation; set => this.RaiseAndSetIfChanged(ref _DownloadLocation, value); }
private int _queueProgress;
public int QueueProgress { get => _queueProgress; set => this.RaiseAndSetIfChanged(ref _queueProgress, value); }
public string LogFile { get; }
private void ExecuteChangePath()
{
switch (this.Mode)
{
case RunMode.Compile:
Location = UIUtils.ShowFolderSelectionDialog("Select Your MO2 profile directory");
break;
case RunMode.Install:
var folder = UIUtils.ShowFolderSelectionDialog("Select Installation directory");
if (folder == null) return;
Location = folder;
if (DownloadLocation == null)
{
DownloadLocation = Path.Combine(Location, "downloads");
}
break;
default:
throw new NotImplementedException();
}
}
private void ExecuteChangeDownloadPath()
{
var folder = UIUtils.ShowFolderSelectionDialog("Select a location for MO2 downloads");
if (folder != null) DownloadLocation = folder;
}
private void ShowReport()
{
var file = Path.GetTempFileName() + ".html";
File.WriteAllText(file, HTMLReport);
Process.Start(file);
}
private ModlistPropertiesWindow modlistPropertiesWindow;
public string newImagePath;
public string readmePath;
public bool ChangedProperties;
private void OpenModListProperties()
{
if (UIReady)
{
if (modlistPropertiesWindow == null)
{
modlistPropertiesWindow = new ModlistPropertiesWindow(this);
newImagePath = null;
ChangedProperties = false;
}
if(!modlistPropertiesWindow.IsClosed)
modlistPropertiesWindow.Show();
else
{
modlistPropertiesWindow = null;
OpenModListProperties();
}
}
}
private void OpenReadmeWindow()
{
if (string.IsNullOrEmpty(this.ModList.Readme)) return;
using (var fs = new FileStream(this.ModListPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var ar = new ZipArchive(fs, ZipArchiveMode.Read))
using (var ms = new MemoryStream())
{
var entry = ar.GetEntry(this.ModList.Readme);
using (var e = entry.Open())
{
e.CopyTo(ms);
}
ms.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(ms))
{
var viewer = new TextViewer(reader.ReadToEnd(), this.ModListName);
viewer.Show();
}
}
}
public string Error => "Error";
public string this[string columnName] => Validate(columnName);
private string Validate(string columnName)
{
string validationMessage = null;
switch (columnName)
{
case "Location":
if (Location == null)
{
validationMessage = null;
}
else switch (Mode)
{
case RunMode.Compile when Location != null && Directory.Exists(Location) && File.Exists(Path.Combine(Location, "modlist.txt")):
Location = Path.Combine(Location, "modlist.txt");
validationMessage = null;
ConfigureForBuild();
break;
case RunMode.Install when Location != null && Directory.Exists(Location) && !Directory.EnumerateFileSystemEntries(Location).Any():
validationMessage = null;
break;
case RunMode.Install when Location != null && Directory.Exists(Location) && Directory.EnumerateFileSystemEntries(Location).Any():
validationMessage = "You have selected a non-empty directory. Installing the modlist here might result in a broken install!";
break;
default:
validationMessage = "Invalid Mod Organizer profile directory";
break;
}
break;
}
return validationMessage;
}
public void LogMsg(string msg)
{
Application.Current.Dispatcher.Invoke(() => Log.Add(msg));
}
public void SetQueueSize(int max, int current)
{
if (max == 0)
max = 1;
var total = current * 100 / max;
QueueProgress = total;
}
private void ConfigureForBuild()
{
var profile_folder = Path.GetDirectoryName(Location);
var mo2folder = Path.GetDirectoryName(Path.GetDirectoryName(profile_folder));
if (!File.Exists(Path.Combine(mo2folder, "ModOrganizer.exe")))
LogMsg($"Error! No ModOrganizer2.exe found in {mo2folder}");
var profile_name = Path.GetFileName(profile_folder);
this.ModListName = profile_name;
this.Mode = RunMode.Compile;
if (Utils.IsMO2Running(mo2folder))
{
MessageBox.Show("You need to close MO2 before running Wabbajack!",
"Error", MessageBoxButton.OK);
Environment.Exit(1);
}
var tmp_compiler = new Compiler(mo2folder);
DownloadLocation = tmp_compiler.MO2DownloadsFolder;
_mo2Folder = mo2folder;
}
internal void ConfigureForInstall(string source, ModList modlist)
{
this.ModList = modlist;
this.ModListPath = source;
this.Mode = RunMode.Install;
ModListName = this.ModList.Name;
HTMLReport = this.ModList.ReportHTML;
Location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var currentWJVersion = new Version(WabbajackVersion);
var modlistWJVersion = new Version(modlist.WabbajackVersion);
if (currentWJVersion > modlistWJVersion)
{
MessageBox.Show(
"The selected Modlist was build with an earlier version of Wabbajack. " +
$"Current Version: {WabbajackVersion}, " +
$"Version used to build the Modlist: {modlist.WabbajackVersion}",
"Information",
MessageBoxButton.OK);
}
else if(currentWJVersion < modlistWJVersion)
{
MessageBox.Show(
"The selected Modlist was build with a newer version of Wabbajack. " +
$"Current Version: {WabbajackVersion}, " +
$"Version used to build the Modlist: {modlist.WabbajackVersion}",
"Information",
MessageBoxButton.OK);
}
this.Slideshow.SlideShowElements = modlist.Archives
.Select(m => m.State)
.OfType<NexusDownloader.State>()
.Select(m =>
new Slide(NexusApiUtils.FixupSummary(m.ModName),m.ModID,
NexusApiUtils.FixupSummary(m.Summary), NexusApiUtils.FixupSummary(m.Author),
m.Adult,m.NexusURL,m.SlideShowPic)).ToList();
this.Slideshow.PreloadSlideShow();
}
private void ExecuteBegin()
{
UIReady = false;
if (this.Mode == RunMode.Install)
{
this.Installing = true;
var installer = new Installer(this.ModListPath, this.ModList, Location)
{
DownloadFolder = DownloadLocation
};
var th = new Thread(() =>
{
UIReady = false;
try
{
installer.Install();
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
LogMsg(ex.StackTrace);
LogMsg(ex.ToString());
LogMsg($"{ex.Message} - Can't continue");
}
finally
{
UIReady = true;
this.Installing = false;
}
})
{
Priority = ThreadPriority.BelowNormal
};
th.Start();
}
else if (_mo2Folder != null)
{
var compiler = new Compiler(_mo2Folder)
{
MO2Profile = ModListName,
ModListName = ChangedProperties ? this.Slideshow.ModName : null,
ModListAuthor = ChangedProperties ? this.Slideshow.AuthorName : null,
ModListDescription = ChangedProperties ? this.Slideshow.Summary : null,
ModListImage = ChangedProperties ? newImagePath : null,
ModListWebsite = ChangedProperties ? this.Slideshow.NexusSiteURL : null,
ModListReadme = ChangedProperties ? readmePath : null,
WabbajackVersion = WabbajackVersion
};
var th = new Thread(() =>
{
UIReady = false;
try
{
compiler.Compile();
if (compiler.ModList != null && compiler.ModList.ReportHTML != null)
HTMLReport = compiler.ModList.ReportHTML;
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
LogMsg(ex.StackTrace);
LogMsg(ex.ToString());
LogMsg($"{ex.Message} - Can't continue");
}
finally
{
UIReady = true;
}
})
{
Priority = ThreadPriority.BelowNormal
};
th.Start();
}
else
{
Utils.Log("Cannot compile modlist: no valid Mod Organizer profile directory selected.");
UIReady = true;
}
}
}
public class CPUStatus
{
public int Progress { get; internal set; }
public string Msg { get; internal set; }
public int ID { get; internal set; }
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace Wabbajack
{
[ValueConversion(typeof(Visibility), typeof(bool))]
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(Visibility))
throw new InvalidOperationException($"The target must be of type {nameof(Visibility)}");
bool compareTo = true;
if (parameter is bool p)
{
compareTo = p;
}
else if (parameter is string str && str.ToUpper().Equals("FALSE"))
{
compareTo = false;
}
return ((bool)value) == compareTo ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -5,6 +5,7 @@ using System.Reactive;
using System.Reactive.Concurrency; using System.Reactive.Concurrency;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks;
using DynamicData; using DynamicData;
using DynamicData.Kernel; using DynamicData.Kernel;
using ReactiveUI; using ReactiveUI;
@ -151,6 +152,57 @@ namespace Wabbajack
}); });
} }
public static IObservable<Unit> SelectTask<T>(this IObservable<T> source, Func<T, Task> task)
{
return source
.SelectMany(async i =>
{
await task(i).ConfigureAwait(false);
return System.Reactive.Unit.Default;
});
}
public static IObservable<Unit> SelectTask<T>(this IObservable<T> source, Func<Task> task)
{
return source
.SelectMany(async _ =>
{
await task().ConfigureAwait(false);
return System.Reactive.Unit.Default;
});
}
public static IObservable<R> SelectTask<T, R>(this IObservable<T> source, Func<Task<R>> task)
{
return source
.SelectMany(_ => task());
}
public static IObservable<R> SelectTask<T, R>(this IObservable<T> source, Func<T, Task<R>> task)
{
return source
.SelectMany(x => task(x));
}
public static IObservable<T> DoTask<T>(this IObservable<T> source, Func<T, Task> task)
{
return source
.SelectMany(async (x) =>
{
await task(x).ConfigureAwait(false);
return x;
});
}
public static IObservable<R> WhereCastable<T, R>(this IObservable<T> source)
where R : class
where T : class
{
return source
.Select(x => x as R)
.NotNull();
}
/// These snippets were provided by RolandPheasant (author of DynamicData) /// These snippets were provided by RolandPheasant (author of DynamicData)
/// They'll be going into the official library at some point, but are here for now. /// They'll be going into the official library at some point, but are here for now.
#region Dynamic Data EnsureUniqueChanges #region Dynamic Data EnsureUniqueChanges

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 512 B

After

Width:  |  Height:  |  Size: 512 B

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

View File

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

View File

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

View File

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -7,7 +7,7 @@
mc:Ignorable="d"> mc:Ignorable="d">
<!--Converters--> <!--Converters-->
<BooleanToVisibilityConverter x:Key="bool2VisibilityConverter"/> <local:BoolToVisibilityConverter x:Key="bool2VisibilityConverter" />
<local:IsNotNullVisibilityConverter x:Key="IsNotNullVisibilityConverter"/> <local:IsNotNullVisibilityConverter x:Key="IsNotNullVisibilityConverter"/>
<!--Colors--> <!--Colors-->
@ -23,11 +23,42 @@
<Color x:Key="HighlightColor">#BDBDBD</Color> <Color x:Key="HighlightColor">#BDBDBD</Color>
<Color x:Key="HotColor">#525252</Color> <Color x:Key="HotColor">#525252</Color>
<!--Brushes--> <Color x:Key="Yellow">#ffc400</Color>
<Color x:Key="Red">#e83a40</Color>
<Color x:Key="Green">#52b545</Color>
<Color x:Key="Primary">#BB86FC</Color>
<Color x:Key="PrimaryVariant">#3700B3</Color>
<Color x:Key="Secondary">#03DAC6</Color>
<Color x:Key="Complementary">#C7FC86</Color>
<Color x:Key="Analogous1">#868CFC</Color>
<Color x:Key="Analogous2">#F686FC</Color>
<Color x:Key="Triadic1">#FC86C7</Color>
<Color x:Key="Triadic2">#FCBB86</Color>
<!-- Brushes -->
<SolidColorBrush x:Key="YellowBrush" Color="{StaticResource Yellow}" />
<SolidColorBrush x:Key="GreenBrush" Color="{StaticResource Green}" />
<SolidColorBrush x:Key="RedBrush" Color="{StaticResource Red}" />
<SolidColorBrush x:Key="WarningBrush" Color="{StaticResource Yellow}" />
<SolidColorBrush x:Key="ErrorBrush" Color="{StaticResource Red}" />
<SolidColorBrush x:Key="DarkBackgroundBrush" Color="{StaticResource DarkBackgroundColor}" />
<SolidColorBrush x:Key="LightBackgroundBrush" Color="{StaticResource LightBackgroundColor}" />
<SolidColorBrush x:Key="BackgroundBrush" Color="{StaticResource BackgroundColor}" />
<SolidColorBrush x:Key="ForegroundBrush" Color="{StaticResource ForegroundColor}"/> <SolidColorBrush x:Key="ForegroundBrush" Color="{StaticResource ForegroundColor}"/>
<SolidColorBrush x:Key="MouseOverForegroundBrush" Color="{StaticResource DarkBackgroundColor}"/> <SolidColorBrush x:Key="MouseOverForegroundBrush" Color="{StaticResource DarkBackgroundColor}"/>
<SolidColorBrush x:Key="WindowBackgroundBrush" Color="{StaticResource WindowBackgroundColor}"/> <SolidColorBrush x:Key="WindowBackgroundBrush" Color="{StaticResource WindowBackgroundColor}"/>
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}" />
<SolidColorBrush x:Key="PrimaryVariantBrush" Color="{StaticResource PrimaryVariant}" />
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}" />
<SolidColorBrush x:Key="ComplementaryBrush" Color="{StaticResource Complementary}" />
<SolidColorBrush x:Key="Analogous1Brush" Color="{StaticResource Analogous1}" />
<SolidColorBrush x:Key="Analogous2Brush" Color="{StaticResource Analogous2}" />
<SolidColorBrush x:Key="Triadic1Brush" Color="{StaticResource Triadic1}" />
<SolidColorBrush x:Key="Triadic2Brush" Color="{StaticResource Triadic2}" />
<SolidColorBrush x:Key="TabControlNormalBorderBrush" Color="{StaticResource WindowBackgroundColor}"/> <SolidColorBrush x:Key="TabControlNormalBorderBrush" Color="{StaticResource WindowBackgroundColor}"/>
<SolidColorBrush x:Key="TabItemHotBackground" Color="{StaticResource HotColor}"/> <SolidColorBrush x:Key="TabItemHotBackground" Color="{StaticResource HotColor}"/>
@ -807,7 +838,7 @@
</Style> </Style>
<!-- TextBox --> <!-- TextBox -->
<Style BasedOn="{StaticResource {x:Type Control}}" TargetType="{x:Type TextBox}"> <Style x:Key="MainTextBoxStyle" BasedOn="{StaticResource {x:Type Control}}" TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="{StaticResource TextBoxBackground}"/> <Setter Property="Background" Value="{StaticResource TextBoxBackground}"/>
<Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/> <Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
<Setter Property="BorderThickness" Value="1"/> <Setter Property="BorderThickness" Value="1"/>
@ -852,6 +883,7 @@
</MultiTrigger>--> </MultiTrigger>-->
</Style.Triggers> </Style.Triggers>
</Style> </Style>
<Style BasedOn="{StaticResource MainTextBoxStyle}" TargetType="TextBox" />
<!-- ComboBox --> <!-- ComboBox -->
<Style x:Key="ComboBoxFocusVisual"> <Style x:Key="ComboBoxFocusVisual">
@ -1151,6 +1183,7 @@
<Setter Property="Background" Value="{StaticResource ButtonBackground}"/> <Setter Property="Background" Value="{StaticResource ButtonBackground}"/>
<Setter Property="BorderBrush" Value="{StaticResource ButtonBorder}"/> <Setter Property="BorderBrush" Value="{StaticResource ButtonBorder}"/>
<Setter Property="BorderThickness" Value="1"/> <Setter Property="BorderThickness" Value="1"/>
<Setter Property="Focusable" Value="False" />
<Setter Property="Foreground" Value="{StaticResource ButtonForeground}"/> <Setter Property="Foreground" Value="{StaticResource ButtonForeground}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/> <Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="VerticalContentAlignment" Value="Center"/>
@ -1189,6 +1222,10 @@
</Style> </Style>
<Style BasedOn="{StaticResource MainButtonStyle}" TargetType="{x:Type Button}" /> <Style BasedOn="{StaticResource MainButtonStyle}" TargetType="{x:Type Button}" />
<Style TargetType="ButtonBase" x:Key="CircleButtonStyle" BasedOn="{StaticResource MahApps.Metro.Styles.MetroCircleButtonStyle}" >
<Setter Property="Focusable" Value="False" />
</Style>
<!-- ToggleButton--> <!-- ToggleButton-->
<Style TargetType="{x:Type ToggleButton}"> <Style TargetType="{x:Type ToggleButton}">
<Setter Property="FocusVisualStyle" Value="{StaticResource ButtonFocusVisual}"/> <Setter Property="FocusVisualStyle" Value="{StaticResource ButtonFocusVisual}"/>

View File

@ -1,103 +0,0 @@
using Alphaleonis.Win32.Filesystem;
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using Wabbajack.Common;
using Wabbajack.Lib;
using Application = System.Windows.Application;
using MessageBox = System.Windows.MessageBox;
namespace Wabbajack
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private AppState _state;
public MainWindow()
{
string[] args = Environment.GetCommandLineArgs();
if (args.Length != 3) return;
var modlistPath = args[2];
var mainWindow = new MainWindow(RunMode.Install, modlistPath);
mainWindow.Show();
Close();
}
public MainWindow(RunMode mode, string source)
{
InitializeComponent();
var context = new AppState(mode);
context.LogMsg($"Wabbajack Build - {ThisAssembly.Git.Sha}");
SetupHandlers(context);
DataContext = context;
Utils.SetLoggerFn(s => context.LogMsg(s));
Utils.SetStatusFn((msg, progress) => WorkQueue.Report(msg, progress));
new Thread(() =>
{
if (mode == RunMode.Compile)
{
Utils.Log("Compiler ready to execute");
context.Location = Path.GetDirectoryName(source);
context.LocationLabel = "MO2 Profile:";
}
else if (mode == RunMode.Install)
{
context.UIReady = false;
context.LocationLabel = "Installation Location:";
var modlist = Installer.LoadFromFile(source);
if (modlist == null)
{
MessageBox.Show("Invalid Modlist, or file not found.", "Invalid Modlist", MessageBoxButton.OK,
MessageBoxImage.Error);
Dispatcher.Invoke(() =>
{
ExitWhenClosing = false;
var window = new ModeSelectionWindow
{
ShowActivated = true
};
window.Show();
Close();
});
}
else
{
context.ConfigureForInstall(source, modlist);
}
}
context.UIReady = true;
}).Start();
}
private void SetupHandlers(AppState state)
{
_state = state;
AppDomain.CurrentDomain.UnhandledException += AppHandler;
}
private void AppHandler(object sender, UnhandledExceptionEventArgs e)
{
_state.LogMsg("Uncaught error:");
_state.LogMsg(((Exception)e.ExceptionObject).ExceptionToString());
}
internal bool ExitWhenClosing = true;
private void Window_Closing(object sender, CancelEventArgs e)
{
if (ExitWhenClosing)
Application.Current.Shutdown();
}
}
}

View File

@ -1,68 +0,0 @@
<Window x:Class="Wabbajack.ModeSelectionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Style="{StaticResource {x:Type Window}}" Icon="Icons/wabbajack.ico" WindowStyle="ToolWindow"
xmlns:local="clr-namespace:Wabbajack"
mc:Ignorable="d"
Title="Wabbajack" Height="800" Width="1024" ResizeMode="CanResize"
Closing="Close_Window">
<Grid Margin="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="150"/>
<RowDefinition Height="*"/>
<RowDefinition Height="70"/>
<RowDefinition Height="70"/>
<RowDefinition Height="70"/>
</Grid.RowDefinitions>
<Image MouseLeftButtonDown="GitHub_MouseLeftButtonDown" Margin="5,0,0,0" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" Name="GitHub"/>
<Image MouseLeftButtonDown="Patreon_MouseLeftButtonDown" Margin="5,0,0,0" Grid.Row="0" Grid.Column="1" Name="Patreon"/>
<Image MouseLeftButtonDown="Discord_MouseLeftButtonDown" Margin="5,0,0,0" Grid.Row="0" Grid.Column="2" Name="Discord"/>
<Image Grid.Row="1" Grid.ColumnSpan="3" Name="Banner" Stretch="Uniform" Margin="2,0,2,0"/>
<ListBox ScrollViewer.CanContentScroll="False" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Grid.Row="2" Grid.ColumnSpan="3"
ItemsSource="{Binding ModLists}" SelectedItem="{Binding Path=SelectedModList, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"></ColumnDefinition>
<ColumnDefinition Width="20"></ColumnDefinition>
<ColumnDefinition Width="200"></ColumnDefinition>
<ColumnDefinition Width="200"></ColumnDefinition>
<ColumnDefinition Width="200"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="15"></RowDefinition>
<RowDefinition Height="150"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<Image Source="{Binding Links.ImageUri}" Grid.Column="0" Grid.Row="0" Grid.RowSpan="4"></Image>
<TextBlock Text="{Binding Title}" FontSize="20" Grid.Column="2" Grid.Row="0"></TextBlock>
<TextBlock Text="{Binding Author}" Grid.Column="2" Grid.Row="1"></TextBlock>
<TextBlock Text="{Binding Game}" HorizontalAlignment="Right" TextAlignment="Right" Grid.Column="4" Grid.Row="1"></TextBlock>
<TextBlock Text="{Binding Description}" VerticalAlignment="Top" HorizontalAlignment="Left" TextWrapping="Wrap" Grid.Column="2" Grid.Row="2" Grid.ColumnSpan="3"></TextBlock>
<Button Grid.Row="3" Grid.Column="2">More Info</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Name="InstallModlist" Grid.ColumnSpan="3" Grid.Row="3" Margin="2" Click="InstallModlist_Click" IsEnabled="{Binding CanInstall}">
<TextBlock FontSize="40">Download and Install</TextBlock>
</Button>
<Button Name="InstallFromList" Grid.ColumnSpan="3" Grid.Row="4" Margin="2" Click="InstallFromList_Click">
<TextBlock FontSize="40">Install from Disk</TextBlock>
</Button>
<Button Name="CreateModlist" Grid.ColumnSpan="3" Grid.Row="5" Margin="2" Click="CreateModlist_Click">
<TextBlock FontSize="40">Create a ModList</TextBlock>
</Button>
</Grid>
</Window>

View File

@ -1,60 +0,0 @@
<Window x:Class="Wabbajack.ModlistPropertiesWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Wabbajack"
mc:Ignorable="d"
Title="Wabbajack (Modlist Properties)" Height="600" Width="900"
Style="{StaticResource {x:Type Window}}" Icon="Icons/wabbajack.ico" WindowStyle="ToolWindow"
ResizeMode="NoResize">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="300"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Grid.Row="0" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Stretch="Fill" Name="SplashScreenProperty"/>
<Button Height="30" Grid.Row="1" Click="SetSplashScreen_Click" ToolTip="Use a 1400x900 png file for the best results">
<TextBlock Text="Change splash screen image" FontSize="15" FontWeight="Bold"/>
</Button>
</Grid>
<Grid Grid.Column="1" Grid.Row="0" Margin="0 5 0 5">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="ModList Name" Name="ModlistNameProperty" ToolTip="Change the name of your ModList" MaxLength="50" AcceptsTab="False" FontSize="15" FontWeight="Bold" Margin="0,0,0,5"/>
<TextBox Grid.Row="1" Text="Author" Name="ModlistAuthorProperty" ToolTip="Change the name of the Author" MaxLength="50" AcceptsTab="False" FontSize="15" Margin="0 0 0 5" />
<TextBox Grid.Row="2" Text="Description (700 characters max)" Name="ModlistDescriptionProperty" ToolTip="Change the description" MaxLength="700" AcceptsReturn="True" TextWrapping="Wrap" AcceptsTab="False" FontSize="15" Margin="0,0,0,0"/>
</Grid>
<TextBox Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Text="Website" Name="ModlistWebsiteProperty" ToolTip="Change the website" MaxLength="80" AcceptsReturn="False" AcceptsTab="False" FontSize="15"/>
<Grid Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" Height="30" Margin="0,10,0,-10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox Height="30" Grid.ColumnSpan="3" Text="Readme path" Name="ModlistReadmeProperty" AcceptsReturn="False" AcceptsTab="False" FontSize="15"/>
<Button Height="30" Grid.Column="2" Content="Choose" Click="ChooseReadme_Click"/>
</Grid>
<Button Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="2" Click="SaveProperties_Click" Margin="0,160,0,-160" Height="30" VerticalAlignment="Bottom">
<TextBlock Text="Save" FontSize="15" FontWeight="Bold"/>
</Button>
</Grid>
</Window>

View File

@ -1,75 +0,0 @@
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media.Imaging;
using Wabbajack.Lib;
namespace Wabbajack
{
/// <summary>
/// Interaction logic for ModlistPropertiesWindow.xaml
/// </summary>
public partial class ModlistPropertiesWindow : Window
{
internal string newBannerFile;
internal readonly AppState state;
internal ModlistPropertiesWindow(AppState _state)
{
InitializeComponent();
var bannerImage = UIUtils.BitmapImageFromResource("Wabbajack.UI.Banner_Dark.png");
SplashScreenProperty.Source = bannerImage;
newBannerFile = null;
state = _state;
}
private void SetSplashScreen_Click(object sender, RoutedEventArgs e)
{
var file = UIUtils.OpenFileDialog("Banner image|*.png");
if (file != null)
{
newBannerFile = file;
SplashScreenProperty.Source = new BitmapImage(new Uri(file));
}
}
private void SaveProperties_Click(object sender, RoutedEventArgs e)
{
if (state.UIReady)
{
if (newBannerFile != null)
{
BitmapImage splashScreen = new BitmapImage(new Uri(newBannerFile));
state.newImagePath = newBannerFile;
state.Slideshow.Image = splashScreen;
}
state.Slideshow.ModName = ModlistNameProperty.Text;
state.Slideshow.Summary = ModlistDescriptionProperty.Text;
state.Slideshow.AuthorName = ModlistAuthorProperty.Text;
state.Slideshow.NexusSiteURL = ModlistWebsiteProperty.Text;
state.readmePath = ModlistReadmeProperty.Text;
state.ChangedProperties = true;
Hide();
}
}
public bool IsClosed { get; private set; }
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
IsClosed = true;
}
private void ChooseReadme_Click(object sender, RoutedEventArgs e)
{
var file = UIUtils.OpenFileDialog("Readme|*.txt");
if (file != null)
{
ModlistReadmeProperty.Text = file;
}
}
}
}

View File

@ -1,122 +0,0 @@
<UserControl
x:Class="Wabbajack.SlideshowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Wabbajack"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignInstance local:SlideShow}"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="4" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid
Grid.Row="1"
Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Source="{Binding Image}"
Stretch="Fill" />
<Button
Grid.Row="1"
FontSize="15"
FontWeight="Bold">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource MainButtonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding AppState.Mode}" Value="{x:Static local:RunMode.Install}">
<Setter Property="Command" Value="{Binding AppState.OpenReadmeCommand}" />
<Setter Property="Content" Value="Open README" />
</DataTrigger>
<DataTrigger Binding="{Binding AppState.Mode}" Value="{x:Static local:RunMode.Compile}">
<Setter Property="Command" Value="{Binding AppState.OpenModListPropertiesCommand}" />
<Setter Property="Content" Value="Modlist Properties" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
<Grid
Grid.Row="1"
Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
FontSize="30"
FontWeight="Bold"
Margin="6,0,0,0"
Text="{Binding ModName}" />
<TextBlock
Grid.Row="1"
FontSize="15"
FontWeight="Bold"
Margin="6,0,0,6"
Text="{Binding AuthorName}" />
<TextBlock
Grid.Row="2"
FontSize="15"
FontWeight="Bold"
Padding="5"
Background="{StaticResource TextBoxBackground}"
Text="{Binding Summary}"
TextWrapping="Wrap" />
<Grid Grid.Row="3" VerticalAlignment="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="48" />
</Grid.ColumnDefinitions>
<CheckBox
Margin="6,0,0,0"
Grid.Column="0"
VerticalAlignment="Center"
IsChecked="{Binding Enable}">
Enable the Slideshow
</CheckBox>
<CheckBox
Grid.Column="1"
Margin="15,0,0,0"
VerticalAlignment="Center"
IsChecked="{Binding ShowNSFW}">
Show NSFW Mods in the Slideshow
</CheckBox>
<Button
Grid.Column="2"
Height="30"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding SlideShowNextItemCommand}"
ToolTip="Skip to next slide">
<DockPanel>
<Image Source="{Binding NextIcon}" Stretch="Fill" />
</DockPanel>
</Button>
</Grid>
<Button
Grid.Row="4"
Height="30"
Command="{Binding VisitNexusSiteCommand}">
<TextBlock
FontSize="15"
FontWeight="Bold"
Text="View Nexus Site" />
</Button>
</Grid>
</Grid>
</UserControl>

View File

@ -1,15 +0,0 @@
<Window x:Class="Wabbajack.UI.TextViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Wabbajack.UI"
Style="{StaticResource {x:Type Window}}"
mc:Ignorable="d"
Icon="Icons/wabbajack.ico"
WindowStyle="ToolWindow"
Title="TextViewer" Height="450" Width="800">
<Grid>
<TextBlock FontSize="20" Name="TextBlock" TextWrapping="Wrap"/>
</Grid>
</Window>

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wabbajack
{
public class CPUStatus
{
public int Progress { get; internal set; }
public string Msg { get; internal set; }
public int ID { get; internal set; }
}
}

View File

@ -0,0 +1,148 @@
using ReactiveUI;
using Splat;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using Wabbajack.Common;
using Wabbajack.Lib;
namespace Wabbajack
{
public class CompilerVM : ViewModel
{
public MainWindowVM MWVM { get; }
private string _Mo2Folder;
public string Mo2Folder { get => _Mo2Folder; set => this.RaiseAndSetIfChanged(ref _Mo2Folder, value); }
private string _MOProfile;
public string MOProfile { get => _MOProfile; set => this.RaiseAndSetIfChanged(ref _MOProfile, value); }
private string _ModListName;
public string ModListName { get => _ModListName; set => this.RaiseAndSetIfChanged(ref _ModListName, value); }
private string _Location;
public string Location { get => _Location; set => this.RaiseAndSetIfChanged(ref _Location, value); }
private bool _UIReady = true;
public bool UIReady { get => _UIReady; set => this.RaiseAndSetIfChanged(ref _UIReady, value); }
private string _AuthorName;
public string AuthorName { get => _AuthorName; set => this.RaiseAndSetIfChanged(ref _AuthorName, value); }
private string _Summary = "Description (700 characters max)";
public string Summary { get => _Summary; set => this.RaiseAndSetIfChanged(ref _Summary, value); }
private string _ImagePath;
public string ImagePath { get => _ImagePath; set => this.RaiseAndSetIfChanged(ref _ImagePath, value); }
private readonly ObservableAsPropertyHelper<BitmapImage> _Image;
public BitmapImage Image => _Image.Value;
private string _NexusSiteURL;
public string NexusSiteURL { get => _NexusSiteURL; set => this.RaiseAndSetIfChanged(ref _NexusSiteURL, value); }
private string _ReadMeText;
public string ReadMeText { get => _ReadMeText; set => this.RaiseAndSetIfChanged(ref _ReadMeText, value); }
private string _HTMLReport;
public string HTMLReport { get => _HTMLReport; set => this.RaiseAndSetIfChanged(ref _HTMLReport, value); }
private string _DownloadLocation;
public string DownloadLocation { get => _DownloadLocation; set => this.RaiseAndSetIfChanged(ref _DownloadLocation, value); }
public IReactiveCommand BeginCommand { get; }
public CompilerVM(MainWindowVM mainWindowVM, string source)
{
this.MWVM = mainWindowVM;
this.Location = source;
this.BeginCommand = ReactiveCommand.CreateFromTask(
execute: this.ExecuteBegin,
canExecute: this.WhenAny(x => x.UIReady)
.ObserveOnGuiThread());
this._Image = this.WhenAny(x => x.ImagePath)
.Select(path =>
{
if (string.IsNullOrWhiteSpace(path)) return UIUtils.BitmapImageFromResource("Wabbajack.Resources.Banner_Dark.png");
if (UIUtils.TryGetBitmapImageFromFile(path, out var image))
{
return image;
}
return UIUtils.BitmapImageFromResource("Wabbajack.Resources.none.png");
})
.ToProperty(this, nameof(this.Image));
ConfigureForBuild(source);
}
private void ConfigureForBuild(string location)
{
var profile_folder = Path.GetDirectoryName(location);
this.Mo2Folder = Path.GetDirectoryName(Path.GetDirectoryName(profile_folder));
if (!File.Exists(Path.Combine(this.Mo2Folder, "ModOrganizer.exe")))
{
this.Log().Error($"Error! No ModOrganizer2.exe found in {this.Mo2Folder}");
}
this.MOProfile = Path.GetFileName(profile_folder);
this.ModListName = this.MOProfile;
var tmp_compiler = new Compiler(this.Mo2Folder);
this.DownloadLocation = tmp_compiler.MO2DownloadsFolder;
}
private async Task ExecuteBegin()
{
if (this.Mo2Folder != null)
{
var compiler = new Compiler(this.Mo2Folder)
{
MO2Profile = this.MOProfile,
ModListName = this.ModListName,
ModListAuthor = this.AuthorName,
ModListDescription = this.Summary,
ModListImage = this.ImagePath,
ModListWebsite = this.NexusSiteURL,
ModListReadme = this.ReadMeText,
};
await Task.Run(() =>
{
UIReady = false;
try
{
compiler.Compile();
if (compiler.ModList?.ReportHTML != null)
{
this.HTMLReport = compiler.ModList.ReportHTML;
}
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
this.Log().Warn(ex, "Can't continue");
}
finally
{
UIReady = true;
}
});
}
else
{
this.Log().Warn("Cannot compile modlist: no valid Mod Organizer profile directory selected.");
UIReady = true;
}
}
}
}

View File

@ -0,0 +1,320 @@
using Syroot.Windows.IO;
using System;
using ReactiveUI;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Reactive.Subjects;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Wabbajack.Common;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.NexusApi;
using DynamicData;
using DynamicData.Binding;
using System.Reactive;
using System.Text;
using Wabbajack.Lib;
using Splat;
namespace Wabbajack
{
public class InstallerVM : ViewModel
{
public SlideShow Slideshow { get; }
public MainWindowVM MWVM { get; }
public BitmapImage WabbajackLogo { get; } = UIUtils.BitmapImageFromResource("Wabbajack.Resources.Wabba_Mouth.png");
private readonly ObservableAsPropertyHelper<ModList> _ModList;
public ModList ModList => _ModList.Value;
private string _ModListPath;
public string ModListPath { get => _ModListPath; set => this.RaiseAndSetIfChanged(ref _ModListPath, value); }
private bool _UIReady;
public bool UIReady { get => _UIReady; set => this.RaiseAndSetIfChanged(ref _UIReady, value); }
private readonly ObservableAsPropertyHelper<string> _HTMLReport;
public string HTMLReport => _HTMLReport.Value;
/// <summary>
/// Tracks whether an install is currently in progress
/// </summary>
private bool _Installing;
public bool Installing { get => _Installing; set => this.RaiseAndSetIfChanged(ref _Installing, value); }
/// <summary>
/// Tracks whether to show the installing pane
/// </summary>
private bool _InstallingMode;
public bool InstallingMode { get => _InstallingMode; set => this.RaiseAndSetIfChanged(ref _InstallingMode, value); }
private string _Location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
public string Location { get => _Location; set => this.RaiseAndSetIfChanged(ref _Location, value); }
private string _DownloadLocation;
public string DownloadLocation { get => _DownloadLocation; set => this.RaiseAndSetIfChanged(ref _DownloadLocation, value); }
private readonly ObservableAsPropertyHelper<float> _ProgressPercent;
public float ProgressPercent => _ProgressPercent.Value;
private readonly ObservableAsPropertyHelper<BitmapImage> _Image;
public BitmapImage Image => _Image.Value;
private readonly ObservableAsPropertyHelper<string> _TitleText;
public string TitleText => _TitleText.Value;
private readonly ObservableAsPropertyHelper<string> _AuthorText;
public string AuthorText => _AuthorText.Value;
private readonly ObservableAsPropertyHelper<string> _Description;
public string Description => _Description.Value;
// Command properties
public IReactiveCommand BeginCommand { get; }
public IReactiveCommand ShowReportCommand { get; }
public IReactiveCommand OpenReadmeCommand { get; }
public IReactiveCommand VisitWebsiteCommand { get; }
public InstallerVM(MainWindowVM mainWindowVM)
{
if (Path.GetDirectoryName(Assembly.GetEntryAssembly().Location.ToLower()) == KnownFolders.Downloads.Path.ToLower())
{
MessageBox.Show(
"Wabbajack is running inside your Downloads folder. This folder is often highly monitored by antivirus software and these can often " +
"conflict with the operations Wabbajack needs to perform. Please move this executable outside of your Downloads folder and then restart the app.",
"Cannot run inside Downloads",
MessageBoxButton.OK,
MessageBoxImage.Error);
Environment.Exit(1);
}
this.MWVM = mainWindowVM;
this._ModList = this.WhenAny(x => x.ModListPath)
.ObserveOn(RxApp.TaskpoolScheduler)
.Select(source =>
{
if (source == null) return default;
var modlist = Installer.LoadFromFile(source);
if (modlist == null)
{
MessageBox.Show("Invalid Modlist, or file not found.", "Invalid Modlist", MessageBoxButton.OK,
MessageBoxImage.Error);
Application.Current.Dispatcher.Invoke(() =>
{
this.MWVM.MainWindow.ExitWhenClosing = false;
var window = new ModeSelectionWindow
{
ShowActivated = true
};
window.Show();
this.MWVM.MainWindow.Close();
});
return default;
}
return modlist;
})
.ObserveOnGuiThread()
.StartWith(default(ModList))
.ToProperty(this, nameof(this.ModList));
this._HTMLReport = this.WhenAny(x => x.ModList)
.Select(modList => modList?.ReportHTML)
.ToProperty(this, nameof(this.HTMLReport));
this._ProgressPercent = Observable.CombineLatest(
this.WhenAny(x => x.Installing),
this.WhenAny(x => x.InstallingMode),
resultSelector: (installing, mode) => !installing && mode)
.Select(show => show ? 1f : 0f)
// Disable for now, until more reliable
//this.WhenAny(x => x.MWVM.QueueProgress)
// .Select(i => i / 100f)
.ToProperty(this, nameof(this.ProgressPercent));
this.Slideshow = new SlideShow(this);
// Locate and create modlist image if it exists
var modListImage = Observable.CombineLatest(
this.WhenAny(x => x.ModList),
this.WhenAny(x => x.ModListPath),
(modList, modListPath) => (modList, modListPath))
.ObserveOn(RxApp.TaskpoolScheduler)
.Select(u =>
{
if (u.modList == null
|| u.modListPath == null
|| !File.Exists(u.modListPath)
|| string.IsNullOrEmpty(u.modList.Image)
|| u.modList.Image.Length != 36)
{
return WabbajackLogo;
}
try
{
using (var fs = new FileStream(u.modListPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var ar = new ZipArchive(fs, ZipArchiveMode.Read))
using (var ms = new MemoryStream())
{
var entry = ar.GetEntry(u.modList.Image);
using (var e = entry.Open())
e.CopyTo(ms);
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = ms;
image.EndInit();
image.Freeze();
return image;
}
}
catch (Exception ex)
{
this.Log().Warn(ex, "Error loading modlist splash image.");
return WabbajackLogo;
}
})
.ObserveOnGuiThread()
.StartWith(default(BitmapImage))
.Replay(1)
.RefCount();
// Set display items to modlist if configuring or complete,
// or to the current slideshow data if installing
this._Image = Observable.CombineLatest(
modListImage
.StartWith(default(BitmapImage)),
this.WhenAny(x => x.Slideshow.Image)
.StartWith(default(BitmapImage)),
this.WhenAny(x => x.Installing),
resultSelector: (modList, slideshow, installing) => installing ? slideshow : modList)
.ToProperty(this, nameof(this.Image));
this._TitleText = Observable.CombineLatest(
this.WhenAny(x => x.ModList.Name),
this.WhenAny(x => x.Slideshow.ModName),
this.WhenAny(x => x.Installing),
resultSelector: (modList, mod, installing) => installing ? mod : modList)
.ToProperty(this, nameof(this.TitleText));
this._AuthorText = Observable.CombineLatest(
this.WhenAny(x => x.ModList.Author),
this.WhenAny(x => x.Slideshow.AuthorName),
this.WhenAny(x => x.Installing),
resultSelector: (modList, mod, installing) => installing ? mod : modList)
.ToProperty(this, nameof(this.AuthorText));
this._Description = Observable.CombineLatest(
this.WhenAny(x => x.ModList.Description),
this.WhenAny(x => x.Slideshow.Description),
this.WhenAny(x => x.Installing),
resultSelector: (modList, mod, installing) => installing ? mod : modList)
.ToProperty(this, nameof(this.Description));
// Define commands
this.ShowReportCommand = ReactiveCommand.Create(ShowReport);
this.OpenReadmeCommand = ReactiveCommand.Create(
execute: this.OpenReadmeWindow,
canExecute: this.WhenAny(x => x.ModList)
.Select(modList => !string.IsNullOrEmpty(modList?.Readme))
.ObserveOnGuiThread());
this.BeginCommand = ReactiveCommand.Create(
execute: this.ExecuteBegin,
canExecute: this.WhenAny(x => x.Installing)
.Select(installing => !installing)
.ObserveOnGuiThread());
this.VisitWebsiteCommand = ReactiveCommand.Create(
execute: () => Process.Start(this.ModList.Website),
canExecute: this.WhenAny(x => x.ModList.Website)
.Select(x => x?.StartsWith("https://") ?? false)
.ObserveOnGuiThread());
// Have Installation location updates modify the downloads location if empty
this.WhenAny(x => x.Location)
.Subscribe(installPath =>
{
if (string.IsNullOrWhiteSpace(this.DownloadLocation))
{
this.DownloadLocation = Path.Combine(installPath, "downloads");
}
})
.DisposeWith(this.CompositeDisposable);
}
private void ShowReport()
{
var file = Path.GetTempFileName() + ".html";
File.WriteAllText(file, HTMLReport);
Process.Start(file);
}
private void OpenReadmeWindow()
{
if (string.IsNullOrEmpty(this.ModList.Readme)) return;
using (var fs = new FileStream(this.ModListPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var ar = new ZipArchive(fs, ZipArchiveMode.Read))
using (var ms = new MemoryStream())
{
var entry = ar.GetEntry(this.ModList.Readme);
if (entry == null)
{
Utils.Log($"Tried to open a non-existant readme: {this.ModList.Readme}");
return;
}
using (var e = entry.Open())
{
e.CopyTo(ms);
}
ms.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(ms))
{
var viewer = new TextViewer(reader.ReadToEnd(), this.ModList.Name);
viewer.Show();
}
}
}
private void ExecuteBegin()
{
this.Installing = true;
this.InstallingMode = true;
var installer = new Installer(this.ModListPath, this.ModList, Location)
{
DownloadFolder = DownloadLocation
};
var th = new Thread(() =>
{
try
{
installer.Install();
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log(ex.StackTrace);
Utils.Log(ex.ToString());
Utils.Log($"{ex.Message} - Can't continue");
}
finally
{
this.Installing = false;
}
})
{
Priority = ThreadPriority.BelowNormal
};
th.Start();
}
}
}

View File

@ -0,0 +1,115 @@
using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Wabbajack.Common;
using Wabbajack.Lib;
namespace Wabbajack
{
/// <summary>
/// Main View Model for the application.
/// Keeps track of which sub view is being shown in the window, and has some singleton wiring like WorkQueue and Logging.
/// </summary>
public class MainWindowVM : ViewModel
{
public MainWindow MainWindow { get; }
private readonly ObservableAsPropertyHelper<ViewModel> _ActivePane;
public ViewModel ActivePane => _ActivePane.Value;
private int _QueueProgress;
public int QueueProgress { get => _QueueProgress; set => this.RaiseAndSetIfChanged(ref _QueueProgress, value); }
private readonly Subject<CPUStatus> _statusSubject = new Subject<CPUStatus>();
public IObservable<CPUStatus> StatusObservable => _statusSubject;
public ObservableCollectionExtended<CPUStatus> StatusList { get; } = new ObservableCollectionExtended<CPUStatus>();
private Subject<string> _logSubj = new Subject<string>();
public ObservableCollectionExtended<string> Log { get; } = new ObservableCollectionExtended<string>();
private RunMode _Mode;
public RunMode Mode { get => _Mode; set => this.RaiseAndSetIfChanged(ref _Mode, value); }
public MainWindowVM(RunMode mode, string source, MainWindow mainWindow)
{
this.Mode = mode;
this.MainWindow = mainWindow;
// Set up logging
_logSubj
.ToObservableChangeSet()
.Buffer(TimeSpan.FromMilliseconds(250))
.Where(l => l.Count > 0)
.FlattenBufferResult()
.Top(5000)
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(this.Log)
.Subscribe()
.DisposeWith(this.CompositeDisposable);
Utils.SetLoggerFn(s => _logSubj.OnNext(s));
Utils.SetStatusFn((msg, progress) => WorkQueue.Report(msg, progress));
// Wire mode to drive the active pane
this._ActivePane = this.WhenAny(x => x.Mode)
.Select<RunMode, ViewModel>(m =>
{
switch (m)
{
case RunMode.Compile:
return new CompilerVM(this, source);
case RunMode.Install:
return new InstallerVM(this);
default:
return default;
}
})
.ToProperty(this, nameof(this.ActivePane));
this.WhenAny(x => x.ActivePane)
.ObserveOn(RxApp.TaskpoolScheduler)
.WhereCastable<ViewModel, InstallerVM>()
.Subscribe(vm => vm.ModListPath = source)
.DisposeWith(this.CompositeDisposable);
// Initialize work queue
WorkQueue.Init(
report_function: (id, msg, progress) => this._statusSubject.OnNext(new CPUStatus() { ID = id, Msg = msg, Progress = progress }),
report_queue_size: (max, current) => this.SetQueueSize(max, current));
// Compile progress updates and populate ObservableCollection
this._statusSubject
.ObserveOn(RxApp.TaskpoolScheduler)
.ToObservableChangeSet(x => x.ID)
.Batch(TimeSpan.FromMilliseconds(250))
.EnsureUniqueChanges()
.ObserveOn(RxApp.MainThreadScheduler)
.Sort(SortExpressionComparer<CPUStatus>.Ascending(s => s.ID), SortOptimisations.ComparesImmutableValuesOnly)
.Bind(this.StatusList)
.Subscribe()
.DisposeWith(this.CompositeDisposable);
}
private void SetQueueSize(int max, int current)
{
if (max == 0)
max = 1;
QueueProgress = current * 100 / max;
}
public override void Dispose()
{
base.Dispose();
Utils.SetLoggerFn(s => { });
}
}
}

View File

@ -11,22 +11,9 @@ using Wabbajack.Lib.ModListRegistry;
namespace Wabbajack.UI namespace Wabbajack.UI
{ {
public class ModeSelectionWindowViewModel : ViewModel public class ModeSelectionWindowVM : ViewModel
{ {
public ObservableCollection<ModlistMetadata> ModLists { get; } = new ObservableCollection<ModlistMetadata>(ModlistMetadata.LoadFromGithub());
public ModeSelectionWindowViewModel()
{
_modLists = new ObservableCollection<ModlistMetadata>(ModlistMetadata.LoadFromGithub());
}
private ObservableCollection<ModlistMetadata> _modLists;
public ObservableCollection<ModlistMetadata> ModLists
{
get => _modLists;
}
private ModlistMetadata _selectedModList; private ModlistMetadata _selectedModList;
public ModlistMetadata SelectedModList public ModlistMetadata SelectedModList
@ -60,7 +47,6 @@ namespace Wabbajack.UI
if (window.Result == DownloadWindow.WindowResult.Completed) if (window.Result == DownloadWindow.WindowResult.Completed)
return dest; return dest;
return null; return null;
} }
} }
} }

View File

@ -1,4 +1,5 @@
using ReactiveUI; using ReactiveUI;
using Splat;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -13,6 +14,7 @@ using System.Threading.Tasks;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using Wabbajack.Common; using Wabbajack.Common;
using Wabbajack.Lib; using Wabbajack.Lib;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.NexusApi; using Wabbajack.Lib.NexusApi;
namespace Wabbajack namespace Wabbajack
@ -30,10 +32,9 @@ namespace Wabbajack
public Queue<Slide> SlidesQueue { get; } public Queue<Slide> SlidesQueue { get; }
public AppState AppState { get; } public InstallerVM Installer { get; }
public BitmapImage NextIcon { get; } = UIUtils.BitmapImageFromResource("Wabbajack.UI.Icons.next.png"); public BitmapImage NextIcon { get; } = UIUtils.BitmapImageFromResource("Wabbajack.Resources.Icons.next.png");
public BitmapImage WabbajackLogo { get; } = UIUtils.BitmapImageFromResource("Wabbajack.UI.Banner_Dark.png");
private bool _ShowNSFW; private bool _ShowNSFW;
public bool ShowNSFW { get => _ShowNSFW; set => this.RaiseAndSetIfChanged(ref _ShowNSFW, value); } public bool ShowNSFW { get => _ShowNSFW; set => this.RaiseAndSetIfChanged(ref _ShowNSFW, value); }
@ -53,8 +54,8 @@ namespace Wabbajack
private string _AuthorName = "Halgari & the Wabbajack Team"; private string _AuthorName = "Halgari & the Wabbajack Team";
public string AuthorName { get => _AuthorName; set => this.RaiseAndSetIfChanged(ref _AuthorName, value); } public string AuthorName { get => _AuthorName; set => this.RaiseAndSetIfChanged(ref _AuthorName, value); }
private string _Summary; private string _Description;
public string Summary { get => _Summary; set => this.RaiseAndSetIfChanged(ref _Summary, value); } public string Description { get => _Description; set => this.RaiseAndSetIfChanged(ref _Description, value); }
private string _NexusSiteURL = "https://github.com/wabbajack-tools/wabbajack"; private string _NexusSiteURL = "https://github.com/wabbajack-tools/wabbajack";
public string NexusSiteURL { get => _NexusSiteURL; set => this.RaiseAndSetIfChanged(ref _NexusSiteURL, value); } public string NexusSiteURL { get => _NexusSiteURL; set => this.RaiseAndSetIfChanged(ref _NexusSiteURL, value); }
@ -62,13 +63,13 @@ namespace Wabbajack
public IReactiveCommand SlideShowNextItemCommand { get; } = ReactiveCommand.Create(() => { }); public IReactiveCommand SlideShowNextItemCommand { get; } = ReactiveCommand.Create(() => { });
public IReactiveCommand VisitNexusSiteCommand { get; } public IReactiveCommand VisitNexusSiteCommand { get; }
public SlideShow(AppState appState) public SlideShow(InstallerVM appState)
{ {
SlideShowElements = NexusApiClient.CachedSlideShow.ToList(); SlideShowElements = NexusApiClient.CachedSlideShow.ToList();
CachedSlides = new Dictionary<string, Slide>(); CachedSlides = new Dictionary<string, Slide>();
SlidesQueue = new Queue<Slide>(); SlidesQueue = new Queue<Slide>();
_random = new Random(); _random = new Random();
AppState = appState; Installer = appState;
this.VisitNexusSiteCommand = ReactiveCommand.Create( this.VisitNexusSiteCommand = ReactiveCommand.Create(
execute: () => Process.Start(this.NexusSiteURL), execute: () => Process.Start(this.NexusSiteURL),
@ -77,91 +78,44 @@ namespace Wabbajack
.ObserveOnGuiThread()); .ObserveOnGuiThread());
// Apply modlist properties when it changes // Apply modlist properties when it changes
this.WhenAny(x => x.AppState.ModList) this.WhenAny(x => x.Installer.ModList)
.NotNull() .NotNull()
.Subscribe(modList => .ObserveOnGuiThread()
.Do(modList =>
{ {
this.NexusSiteURL = modList.Website; this.SlideShowElements = modList.Archives
this.ModName = modList.Name; .Select(m => m.State)
this.AuthorName = modList.Author; .OfType<NexusDownloader.State>()
this.Summary = modList.Description; .Select(m =>
new Slide(NexusApiUtils.FixupSummary(m.ModName), m.ModID,
NexusApiUtils.FixupSummary(m.Summary), NexusApiUtils.FixupSummary(m.Author),
m.Adult, m.NexusURL, m.SlideShowPic)).ToList();
}) })
.DisposeWith(this.CompositeDisposable);
// Update splashscreen when modlist changes
Observable.CombineLatest(
(this).WhenAny(x => x.AppState.ModList),
(this).WhenAny(x => x.AppState.ModListPath),
(this).WhenAny(x => x.Enable),
(modList, modListPath, enabled) => (modList, modListPath, enabled))
// Do any potential unzipping on a background thread
.ObserveOn(RxApp.TaskpoolScheduler) .ObserveOn(RxApp.TaskpoolScheduler)
.Select(u => .Do(modList =>
{ {
if (u.enabled // This takes a while, and is currently blocking
&& u.modList != null this.PreloadSlideShow();
&& u.modListPath != null
&& File.Exists(u.modListPath)
&& !string.IsNullOrEmpty(u.modList.Image)
&& u.modList.Image.Length == 36)
{
try
{
using (var fs = new FileStream(u.modListPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var ar = new ZipArchive(fs, ZipArchiveMode.Read))
using (var ms = new MemoryStream())
{
var entry = ar.GetEntry(u.modList.Image);
using (var e = entry.Open())
e.CopyTo(ms);
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = ms;
image.EndInit();
image.Freeze();
return image;
}
}
catch (Exception)
{
this.AppState.LogMsg("Error loading splash image.");
}
}
return this.WabbajackLogo;
}) })
.ObserveOn(RxApp.MainThreadScheduler) .Subscribe()
.StartWith(this.WabbajackLogo)
.Subscribe(bitmap => this.Image = bitmap)
.DisposeWith(this.CompositeDisposable); .DisposeWith(this.CompositeDisposable);
/// Wire slideshow updates /// Wire slideshow updates
var intervalSeconds = 10; // Merge all the sources that trigger a slideshow update
// Compile all the sources that trigger a slideshow update
Observable.Merge( Observable.Merge(
// If user requests one manually
this.SlideShowNextItemCommand.StartingExecution(),
// If the natural timer fires // If the natural timer fires
Observable.Merge( Observable.Interval(TimeSpan.FromSeconds(10))
// Start with an initial timer .Unit()
Observable.Return(Observable.Interval(TimeSpan.FromSeconds(intervalSeconds))), // Only if enabled
// but reset timer if user requests one .FilterSwitch(this.WhenAny(x => x.Enable)),
this.SlideShowNextItemCommand.StartingExecution() // If user requests one manually
.Select(_ => Observable.Interval(TimeSpan.FromSeconds(intervalSeconds)))) this.SlideShowNextItemCommand.StartingExecution())
// When a new timer comes in, swap to it // When installing fire an initial signal
.Switch()
.Unit())
// When filter switch enabled, fire an initial signal
.StartWith(Unit.Default) .StartWith(Unit.Default)
// Only subscribe to slideshow triggers if enabled and installing // Only subscribe to slideshow triggers if installing
.FilterSwitch( .FilterSwitch(this.WhenAny(x => x.Installer.Installing))
Observable.CombineLatest( // Don't ever update more than once every half second. ToDo: Update to debounce
this.WhenAny(x => x.Enable), .Throttle(TimeSpan.FromMilliseconds(500), RxApp.MainThreadScheduler)
this.WhenAny(x => x.AppState.Installing),
resultSelector: (enabled, installing) => enabled && installing))
// Don't ever update more than once every half second.
.Debounce(TimeSpan.FromMilliseconds(500), RxApp.MainThreadScheduler)
.ObserveOn(RxApp.MainThreadScheduler) .ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => this.UpdateSlideShowItem()) .Subscribe(_ => this.UpdateSlideShowItem())
.DisposeWith(this.CompositeDisposable); .DisposeWith(this.CompositeDisposable);
@ -203,7 +157,7 @@ namespace Wabbajack
if (!slide.IsNSFW || (slide.IsNSFW && ShowNSFW)) if (!slide.IsNSFW || (slide.IsNSFW && ShowNSFW))
{ {
this.Image = AppState._noneImage; this.Image = UIUtils.BitmapImageFromResource("Wabbajack.Resources.none.jpg");
if (slide.ImageURL != null && slide.Image != null) if (slide.ImageURL != null && slide.Image != null)
{ {
if (!CachedSlides.ContainsKey(slide.ModID)) return; if (!CachedSlides.ContainsKey(slide.ModID)) return;
@ -212,7 +166,7 @@ namespace Wabbajack
this.ModName = slide.ModName; this.ModName = slide.ModName;
this.AuthorName = slide.ModAuthor; this.AuthorName = slide.ModAuthor;
this.Summary = slide.ModDescription; this.Description = slide.ModDescription;
this.NexusSiteURL = slide.ModURL; this.NexusSiteURL = slide.ModURL;
} }

View File

@ -1,20 +1,12 @@
<Window <UserControl
x:Class="Wabbajack.MainWindow" x:Class="Wabbajack.CompilerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Wabbajack" xmlns:local="clr-namespace:Wabbajack"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Wabbajack" d:DesignHeight="450"
Width="1280" d:DesignWidth="800"
Height="960"
MinWidth="1024"
MinHeight="768"
Closing="Window_Closing"
Icon="Icons/wabbajack.ico"
ResizeMode="CanResize"
Style="{StaticResource {x:Type Window}}"
WindowStyle="ToolWindow"
mc:Ignorable="d"> mc:Ignorable="d">
<Viewbox Stretch="Uniform"> <Viewbox Stretch="Uniform">
<Grid Margin="4,0,4,0"> <Grid Margin="4,0,4,0">
@ -29,8 +21,9 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="640" /> <ColumnDefinition Width="638" />
<ColumnDefinition Width="640" /> <ColumnDefinition Width="4" />
<ColumnDefinition Width="638" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel <StackPanel
@ -40,28 +33,101 @@
<TextBlock <TextBlock
FontSize="16" FontSize="16"
FontWeight="Bold" FontWeight="Bold"
Text="{Binding Mode}" /> Text="Compiling" />
<TextBlock FontSize="16" Text=" : " /> <TextBlock FontSize="16" Text=" : " />
<TextBlock FontSize="16" Text="{Binding ModListName}" /> <TextBlock FontSize="16" Text="{Binding MOProfile}" />
</StackPanel> </StackPanel>
<local:SlideshowView <Grid
x:Name="Slideshow"
Grid.Row="1" Grid.Row="1"
Grid.Column="0" Grid.Column="0"
Grid.ColumnSpan="2" Margin="0,8,0,8"
Margin="0,0,0,4" IsEnabled="{Binding UIReady}">
DataContext="{Binding Slideshow}" /> <Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Source="{Binding Image}"
Stretch="Fill" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="Splash Screen Path:" />
<local:FilePicker
Grid.Column="1"
Width="534"
HorizontalAlignment="Left"
DoExistsCheck="False"
Filter="Banner image|*.png"
IsEnabled="{Binding UIReady}"
PathType="File"
TargetPath="{Binding ImagePath}" />
</Grid>
</Grid>
<ScrollViewer
Grid.Row="1"
Grid.Column="2"
Background="Transparent"
HorizontalScrollBarVisibility="Disabled"
IsEnabled="{Binding UIReady}"
VerticalScrollBarVisibility="Auto">
<StackPanel Background="Transparent" Orientation="Vertical">
<StackPanel.Resources>
<Thickness
x:Key="TitleMargin"
Bottom="1"
Left="5" />
<Style
x:Key="ValueStyle"
BasedOn="{StaticResource MainTextBoxStyle}"
TargetType="TextBox">
<Setter Property="MaxLength" Value="50" />
<Setter Property="AcceptsTab" Value="False" />
<Setter Property="FontSize" Value="15" />
<Setter Property="Margin" Value="0,0,0,6" />
</Style>
</StackPanel.Resources>
<TextBlock Margin="{StaticResource TitleMargin}" Text="ModList Name" />
<TextBox Style="{StaticResource ValueStyle}" Text="{Binding ModListName}" />
<TextBlock Margin="{StaticResource TitleMargin}" Text="Author" />
<TextBox Style="{StaticResource ValueStyle}" Text="{Binding AuthorName}" />
<TextBlock Margin="{StaticResource TitleMargin}" Text="Description" />
<TextBox
Height="150"
AcceptsReturn="True"
AcceptsTab="False"
MaxLength="700"
Style="{StaticResource ValueStyle}"
Text="{Binding Summary}"
TextWrapping="Wrap" />
<TextBlock Margin="{StaticResource TitleMargin}" Text="Website" />
<TextBox Style="{StaticResource ValueStyle}" Text="{Binding NexusSiteURL}" />
<TextBlock
Margin="{StaticResource TitleMargin}"
Text="Readme Path"
ToolTip="Path to a readme file." />
<local:FilePicker
DoExistsCheck="False"
PathType="File"
TargetPath="{Binding ReadMeText}"
ToolTip="Path to a readme file." />
</StackPanel>
</ScrollViewer>
<ProgressBar <ProgressBar
Grid.Row="2" Grid.Row="2"
Grid.Column="0" Grid.Column="0"
Grid.ColumnSpan="2" Grid.ColumnSpan="3"
Margin="1,0,1,0" Margin="1,0,1,0"
Background="#444444" Background="#444444"
Maximum="100" Maximum="100"
Minimum="0" Minimum="0"
Value="{Binding QueueProgress}" /> Value="{Binding MWVM.QueueProgress}" />
<!-- Log --> <!-- Log -->
<TextBlock <TextBlock
@ -73,7 +139,7 @@
Grid.Row="4" Grid.Row="4"
Margin="0,0,2,0" Margin="0,0,2,0"
local:AutoScrollBehavior.ScrollOnNewItem="True" local:AutoScrollBehavior.ScrollOnNewItem="True"
ItemsSource="{Binding Log}" /> ItemsSource="{Binding MWVM.Log}" />
<!-- End Log --> <!-- End Log -->
<!-- Location --> <!-- Location -->
@ -81,12 +147,11 @@
Grid.Row="5" Grid.Row="5"
Grid.RowSpan="2" Grid.RowSpan="2"
Grid.Column="0" Grid.Column="0"
Margin="-4,10,2,10" Margin="-4,10,0,10"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition /> <RowDefinition />
@ -96,35 +161,27 @@
<Label <Label
Grid.Row="0" Grid.Row="0"
Grid.Column="0" Grid.Column="0"
Content="{Binding LocationLabel}" /> Content="Installation Location:" />
<TextBox <local:FilePicker
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
DoExistsCheck="False"
IsEnabled="{Binding UIReady}" IsEnabled="{Binding UIReady}"
Text="{Binding Location, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" /> PathType="Folder"
<Button SetTargetPathCommand="{Binding ChangePathCommand}"
Grid.Row="0" TargetPath="{Binding Location}" />
Grid.Column="2"
MinWidth="80"
Command="{Binding ChangePathCommand}"
Content="Select"
IsEnabled="{Binding UIReady}" />
<Label <Label
Grid.Row="2" Grid.Row="2"
Grid.Column="0" Grid.Column="0"
Content="Download Location:" /> Content="Download Location:" />
<TextBox <local:FilePicker
Grid.Row="2" Grid.Row="2"
Grid.Column="1" Grid.Column="1"
DoExistsCheck="False"
IsEnabled="{Binding UIReady}" IsEnabled="{Binding UIReady}"
Text="{Binding DownloadLocation}" /> PathType="Folder"
<Button SetTargetPathCommand="{Binding ChangeDownloadPathCommand}"
Grid.Row="2" TargetPath="{Binding DownloadLocation}" />
Grid.Column="2"
MinWidth="80"
Command="{Binding ChangeDownloadPathCommand}"
Content="Select"
IsEnabled="{Binding UIReady}" />
</Grid> </Grid>
<!-- End Location --> <!-- End Location -->
@ -132,18 +189,17 @@
<!-- Work Queue Start --> <!-- Work Queue Start -->
<TextBlock <TextBlock
Grid.Row="3" Grid.Row="3"
Grid.Column="1" Grid.Column="2"
Margin="2,16,0,8" Margin="0,16,0,8"
FontSize="14" FontSize="14"
Text="Work Queue:" /> Text="Work Queue:" />
<ListBox <ListBox
Grid.Row="4" Grid.Row="4"
Grid.Column="1" Grid.Column="2"
Width="Auto" Width="Auto"
Margin="2,0,0,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemsSource="{Binding Status}"> ItemsSource="{Binding MWVM.StatusList}">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid HorizontalAlignment="Stretch"> <Grid HorizontalAlignment="Stretch">
@ -184,8 +240,8 @@
<Grid <Grid
Grid.Row="5" Grid.Row="5"
Grid.RowSpan="2" Grid.RowSpan="2"
Grid.Column="1" Grid.Column="2"
Margin="2,10,0,10"> Margin="0,10,0,10">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="30" /> <RowDefinition Height="30" />
<RowDefinition Height="30" /> <RowDefinition Height="30" />
@ -206,4 +262,4 @@
</Grid> </Grid>
</Grid> </Grid>
</Viewbox> </Viewbox>
</Window> </UserControl>

View File

@ -16,11 +16,11 @@ using System.Windows.Shapes;
namespace Wabbajack namespace Wabbajack
{ {
/// <summary> /// <summary>
/// Interaction logic for SlideshowView.xaml /// Interaction logic for CompilerView.xaml
/// </summary> /// </summary>
public partial class SlideshowView : UserControl public partial class CompilerView : UserControl
{ {
public SlideshowView() public CompilerView()
{ {
InitializeComponent(); InitializeComponent();
} }

View File

@ -0,0 +1,45 @@
<local:UserControlRx
x:Class="Wabbajack.FilePicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:local="clr-namespace:Wabbajack"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="35"
d:DesignWidth="400"
BorderBrush="{StaticResource DarkBackgroundBrush}"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox
Grid.Column="0"
Margin="0,0,-4,0"
VerticalContentAlignment="Center"
Background="{StaticResource DarkBackgroundBrush}"
Text="{Binding TargetPath, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding ShowTextBoxInput, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<icon:PackIconMaterial
Margin="0,4,4,4"
Padding="0,3"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Foreground="{StaticResource WarningBrush}"
Kind="Circle"
ToolTip="Path does not exist"
Visibility="{Binding Exists, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource bool2VisibilityConverter}, ConverterParameter=False}" />
<Button
Grid.Column="1"
Command="{Binding SetTargetPathCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
ToolTip="Set target path">
<icon:PackIconMaterial
Width="16"
Height="12"
Margin="4"
Kind="DotsHorizontal" />
</Button>
</Grid>
</local:UserControlRx>

View File

@ -0,0 +1,180 @@
using Microsoft.WindowsAPICodePack.Dialogs;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Wabbajack
{
/// <summary>
/// Interaction logic for FilePicker.xaml
/// </summary>
public partial class FilePicker : UserControlRx
{
public enum PathTypeOptions
{
Off,
Either,
File,
Folder
}
public ICommand SetTargetPathCommand
{
get => (ICommand)GetValue(SetTargetPathCommandProperty);
set => SetValue(SetTargetPathCommandProperty, value);
}
public static readonly DependencyProperty SetTargetPathCommandProperty = DependencyProperty.Register(nameof(SetTargetPathCommand), typeof(ICommand), typeof(FilePicker),
new FrameworkPropertyMetadata(default(ICommand)));
public string TargetPath
{
get { return (string)GetValue(TargetPathProperty); }
set { SetValue(TargetPathProperty, value); }
}
public static readonly DependencyProperty TargetPathProperty = DependencyProperty.Register(nameof(TargetPath), typeof(string), typeof(FilePicker),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, WireNotifyPropertyChanged));
public bool ShowTextBoxInput
{
get => (bool)GetValue(ShowTextBoxInputProperty);
set => SetValue(ShowTextBoxInputProperty, value);
}
public static readonly DependencyProperty ShowTextBoxInputProperty = DependencyProperty.Register(nameof(ShowTextBoxInput), typeof(bool), typeof(FilePicker),
new FrameworkPropertyMetadata(true));
public PathTypeOptions PathType
{
get => (PathTypeOptions)GetValue(PathTypeProperty);
set => SetValue(PathTypeProperty, value);
}
public static readonly DependencyProperty PathTypeProperty = DependencyProperty.Register(nameof(PathType), typeof(PathTypeOptions), typeof(FilePicker),
new FrameworkPropertyMetadata(PathTypeOptions.Off, WireNotifyPropertyChanged));
public bool Exists
{
get => (bool)GetValue(ExistsProperty);
set => SetValue(ExistsProperty, value);
}
public static readonly DependencyProperty ExistsProperty = DependencyProperty.Register(nameof(Exists), typeof(bool), typeof(FilePicker),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, WireNotifyPropertyChanged));
public bool DoExistsCheck
{
get => (bool)GetValue(DoExistsCheckProperty);
set => SetValue(DoExistsCheckProperty, value);
}
public static readonly DependencyProperty DoExistsCheckProperty = DependencyProperty.Register(nameof(DoExistsCheck), typeof(bool), typeof(FilePicker),
new FrameworkPropertyMetadata(true, WireNotifyPropertyChanged));
public string PromptTitle
{
get => (string)GetValue(PromptTitleProperty);
set => SetValue(PromptTitleProperty, value);
}
public static readonly DependencyProperty PromptTitleProperty = DependencyProperty.Register(nameof(PromptTitle), typeof(string), typeof(FilePicker),
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Filter
{
get => (string)GetValue(FilterProperty);
set => SetValue(FilterProperty, value);
}
public static readonly DependencyProperty FilterProperty = DependencyProperty.Register(nameof(Filter), typeof(string), typeof(FilePicker),
new FrameworkPropertyMetadata(default(string)));
public FilePicker()
{
InitializeComponent();
this.SetTargetPathCommand = ReactiveCommand.Create(
execute: () =>
{
string dirPath;
if (File.Exists(this.TargetPath))
{
dirPath = System.IO.Path.GetDirectoryName(this.TargetPath);
}
else
{
dirPath = this.TargetPath;
}
var dlg = new CommonOpenFileDialog();
dlg.Title = this.PromptTitle;
dlg.IsFolderPicker = this.PathType == PathTypeOptions.Folder;
dlg.InitialDirectory = this.TargetPath;
dlg.AddToMostRecentlyUsedList = false;
dlg.AllowNonFileSystemItems = false;
dlg.DefaultDirectory = this.TargetPath;
dlg.EnsureFileExists = true;
dlg.EnsurePathExists = true;
dlg.EnsureReadOnly = false;
if (!string.IsNullOrWhiteSpace(this.Filter))
{
var split = this.Filter.Split('|');
if (split.Length == 2)
{
dlg.Filters.Add(new CommonFileDialogFilter(split[0], split[1]));
}
}
dlg.EnsureValidNames = true;
dlg.Multiselect = false;
dlg.ShowPlacesList = true;
if (dlg.ShowDialog() != CommonFileDialogResult.Ok) return;
this.TargetPath = dlg.FileName;
});
// Check that file exists
Observable.Interval(TimeSpan.FromSeconds(3))
.FilterSwitch(
Observable.CombineLatest(
this.WhenAny(x => x.PathType),
this.WhenAny(x => x.DoExistsCheck),
resultSelector: (type, doExists) => type != PathTypeOptions.Off && doExists))
.Unit()
// Also do it when fields change
.Merge(this.WhenAny(x => x.PathType).Unit())
.Merge(this.WhenAny(x => x.DoExistsCheck).Unit())
.CombineLatest(
this.WhenAny(x => x.DoExistsCheck),
this.WhenAny(x => x.PathType),
this.WhenAny(x => x.TargetPath)
.Throttle(TimeSpan.FromMilliseconds(200)),
resultSelector: (_, DoExists, Type, Path) => (DoExists, Type, Path))
// Refresh exists
.Select(t =>
{
if (!t.DoExists) return true;
switch (t.Type)
{
case PathTypeOptions.Either:
return File.Exists(t.Path) || Directory.Exists(t.Path);
case PathTypeOptions.File:
return File.Exists(t.Path);
case PathTypeOptions.Folder:
return Directory.Exists(t.Path);
default:
return true;
}
})
.DistinctUntilChanged()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(exists => this.Exists = exists)
.DisposeWith(this.CompositeDisposable);
}
}
}

View File

@ -0,0 +1,772 @@
<UserControl
x:Class="Wabbajack.InstallationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:local="clr-namespace:Wabbajack"
xmlns:mahapps="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="500"
d:DesignWidth="800"
mc:Ignorable="d">
<UserControl.Resources>
<Color x:Key="TextBackgroundFill">#92000000</Color>
<SolidColorBrush x:Key="TextBackgroundFillBrush" Color="{StaticResource TextBackgroundFill}" />
<Color x:Key="TextBackgroundHoverFill">#DF000000</Color>
<Style x:Key="BackgroundBlurStyle" TargetType="TextBlock">
<Setter Property="Background" Value="{StaticResource TextBackgroundFillBrush}" />
<Setter Property="Foreground" Value="Transparent" />
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, ElementName=TextHoverTrigger}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="(TextBlock.Background).(SolidColorBrush.Color)"
To="{StaticResource TextBackgroundHoverFill}"
Duration="0:0:0.06" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="(TextBlock.Background).(SolidColorBrush.Color)"
To="{StaticResource TextBackgroundFill}"
Duration="0:0:0.06" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
<DataTrigger Binding="{Binding Image}" Value="{x:Null}">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style
x:Key="SlideshowButton"
BasedOn="{StaticResource CircleButtonStyle}"
TargetType="ButtonBase">
<Setter Property="Width" Value="40" />
<Setter Property="Height" Value="40" />
<Setter Property="Margin" Value="4" />
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="5" />
<RowDefinition Height="4*" />
<RowDefinition Height="*" MinHeight="150" />
</Grid.RowDefinitions>
<Rectangle
x:Name="BorderEdgeFadeDown"
Grid.Row="1"
Grid.RowSpan="3">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="#191919" />
<GradientStop Offset="0.4" Color="#00191919" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<!-- Slideshow -->
<Border
x:Name="Slideshow"
Grid.Row="2"
Margin="5,0,5,5"
BorderBrush="#171717"
BorderThickness="1,0,1,1"
UseLayoutRounding="True">
<Grid ClipToBounds="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle
Grid.Row="0"
Grid.RowSpan="4"
Grid.Column="0"
Grid.ColumnSpan="2"
Fill="{StaticResource WindowBackgroundBrush}" />
<Viewbox
Grid.Row="0"
Grid.RowSpan="4"
Grid.Column="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stretch="UniformToFill">
<Image Source="{Binding Image}" />
</Viewbox>
<Image
Grid.Row="0"
Grid.RowSpan="4"
Grid.Column="0"
Grid.ColumnSpan="2"
Width="60"
Height="60"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Source="{Binding ModlistImage}" />
<TextBlock
x:Name="TitleTextShadow"
Grid.Row="2"
Grid.Column="0"
Margin="-20,15,40,-10"
Padding="40,10"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
FontFamily="Lucida Sans"
FontSize="65"
FontWeight="Bold"
Style="{StaticResource BackgroundBlurStyle}"
Text="{Binding TitleText}"
TextWrapping="WrapWithOverflow">
<TextBlock.Effect>
<BlurEffect Radius="85" />
</TextBlock.Effect>
</TextBlock>
<TextBlock
x:Name="ArtistTextShadow"
Grid.Row="3"
Grid.Column="0"
Margin="35,-10,-10,10"
Padding="30,10"
HorizontalAlignment="Left"
FontFamily="Lucida Sans"
FontSize="30"
FontWeight="Bold"
Style="{StaticResource BackgroundBlurStyle}"
TextWrapping="WrapWithOverflow">
<TextBlock.Effect>
<BlurEffect Radius="55" />
</TextBlock.Effect>
<Run FontSize="15" Text="by" />
<Run Text="{Binding AuthorText, Mode=OneWay}" />
</TextBlock>
<TextBlock
Grid.Row="2"
Grid.Column="0"
Margin="20,25,20,0"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
FontFamily="Lucida Sans"
FontSize="65"
FontWeight="Bold"
Text="{Binding TitleText}"
TextWrapping="WrapWithOverflow">
<TextBlock.Effect>
<DropShadowEffect />
</TextBlock.Effect>
</TextBlock>
<TextBlock
Grid.Row="3"
Grid.Column="0"
Margin="55,0,20,20"
FontFamily="Lucida Sans"
FontSize="30"
FontWeight="Bold"
TextWrapping="Wrap">
<TextBlock.Effect>
<DropShadowEffect />
</TextBlock.Effect>
<Run FontSize="15" Text="by" />
<Run Text="{Binding AuthorText, Mode=OneWay}" />
</TextBlock>
<Grid
Grid.Row="0"
Grid.Column="1"
Margin="0,20,25,0"
HorizontalAlignment="Right">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.Effect>
<DropShadowEffect
BlurRadius="25"
Opacity="1"
Color="Black" />
</Grid.Effect>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Opacity" Value="0" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsMouseOver, ElementName=Slideshow}" Value="True" />
<Condition Binding="{Binding Installing}" Value="True" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0:0:0.12" />
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
<MultiDataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity">
<LinearDoubleKeyFrame KeyTime="0:0:0.3" Value="1" />
<LinearDoubleKeyFrame KeyTime="0:0:0.42" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Button
Grid.Column="3"
Width="60"
Height="60"
Margin="6"
Background="{StaticResource BackgroundBrush}"
Command="{Binding Slideshow.SlideShowNextItemCommand}"
Style="{StaticResource CircleButtonStyle}"
ToolTip="Skip to the next mod">
<icon:PackIconFontAwesome
Width="28"
Height="28"
Kind="ChevronRightSolid" />
</Button>
<ToggleButton
x:Name="PlayPauseButton"
Grid.Column="2"
Margin="6"
Background="{StaticResource BackgroundBrush}"
IsChecked="{Binding Slideshow.Enable}">
<ToggleButton.Style>
<Style BasedOn="{StaticResource SlideshowButton}" TargetType="ToggleButton">
<Setter Property="ToolTip" Value="Play slideshow" />
<Style.Triggers>
<DataTrigger Binding="{Binding Slideshow.Enable}" Value="True">
<Setter Property="ToolTip" Value="Pause slideshow" />
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
<icon:PackIconMaterial>
<icon:PackIconMaterial.Style>
<Style TargetType="icon:PackIconMaterial">
<Setter Property="Kind" Value="Pause" />
<Style.Triggers>
<DataTrigger Binding="{Binding Slideshow.Enable}" Value="True">
<Setter Property="Kind" Value="Play" />
</DataTrigger>
</Style.Triggers>
</Style>
</icon:PackIconMaterial.Style>
</icon:PackIconMaterial>
</ToggleButton>
<Button
Grid.Column="1"
Margin="6"
Background="{StaticResource BackgroundBrush}"
Command="{Binding Slideshow.VisitNexusSiteCommand}"
Style="{StaticResource SlideshowButton}"
ToolTip="Open Nexus Website">
<icon:PackIconMaterial
Width="28"
Height="28"
Kind="Web" />
</Button>
<ToggleButton
Grid.Column="0"
Background="{StaticResource BackgroundBrush}"
IsChecked="{Binding Slideshow.ShowNSFW}"
ToolTip="Show NSFW mods">
<ToggleButton.Style>
<Style BasedOn="{StaticResource SlideshowButton}" TargetType="ToggleButton">
<Setter Property="ToolTip" Value="Show NSFW mods" />
<Style.Triggers>
<DataTrigger Binding="{Binding Slideshow.ShowNSFW}" Value="True">
<Setter Property="ToolTip" Value="Hide NSFW mods" />
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
<Grid>
<TextBlock
Margin="4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="Lucida Sans"
FontSize="9"
FontWeight="Bold"
Text="NSFW" />
<icon:PackIconOcticons
Width="40"
Height="40"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="#88FFFFFF"
Kind="CircleSlash"
Visibility="{Binding Slideshow.ShowNSFW, Converter={StaticResource bool2VisibilityConverter}, ConverterParameter=False}" />
</Grid>
</ToggleButton>
</Grid>
<TextBlock
x:Name="DescriptionTextShadow"
Grid.Row="2"
Grid.RowSpan="2"
Grid.Column="1"
Margin="-10,15,-5,15"
Padding="30,10"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
FontFamily="Lucida Sans"
FontSize="16"
Style="{StaticResource BackgroundBlurStyle}"
Text="{Binding Description}"
TextAlignment="Right"
TextWrapping="Wrap">
<TextBlock.Effect>
<BlurEffect Radius="55" />
</TextBlock.Effect>
</TextBlock>
<TextBlock
Grid.Row="2"
Grid.RowSpan="2"
Grid.Column="1"
Margin="20,25,25,25"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
FontFamily="Lucida Sans"
FontSize="16"
Text="{Binding Description}"
TextAlignment="Right"
TextWrapping="Wrap">
<TextBlock.Effect>
<DropShadowEffect />
</TextBlock.Effect>
</TextBlock>
<Rectangle
x:Name="TextHoverTrigger"
Grid.Row="2"
Grid.RowSpan="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Fill="Transparent" />
</Grid>
</Border>
<!-- Top progress bar -->
<!-- Comes after slideshow control, so that any effects/overlap goes on top -->
<Rectangle
Grid.Row="2"
Height="25"
Margin="6,0"
VerticalAlignment="Top"
IsHitTestVisible="False">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="#AA000000" />
<GradientStop Offset="1" Color="#00000000" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Grid.Row="1" Fill="{StaticResource BackgroundBrush}" />
<mahapps:MetroProgressBar
x:Name="BottomProgressBarDarkGlow"
Grid.Row="1"
Grid.RowSpan="2"
Height="16"
Margin="-4"
VerticalAlignment="Top"
Background="Transparent"
BorderBrush="Transparent"
Foreground="{StaticResource PrimaryVariantBrush}"
Maximum="1"
Value="{Binding ProgressPercent, Mode=OneWay}">
<mahapps:MetroProgressBar.Effect>
<BlurEffect Radius="25" />
</mahapps:MetroProgressBar.Effect>
</mahapps:MetroProgressBar>
<mahapps:MetroProgressBar
x:Name="BottomProgressBarBrightGlow"
Grid.Row="1"
Grid.RowSpan="2"
Height="6"
Margin="-4"
VerticalAlignment="Top"
Background="Transparent"
BorderBrush="Transparent"
Foreground="{StaticResource PrimaryBrush}"
Maximum="1"
Value="{Binding ProgressPercent, Mode=OneWay}">
<mahapps:MetroProgressBar.Effect>
<BlurEffect Radius="20" />
</mahapps:MetroProgressBar.Effect>
</mahapps:MetroProgressBar>
<Grid x:Name="TopBarGrid" Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<mahapps:MetroProgressBar
Grid.Column="0"
Grid.ColumnSpan="4"
Background="{StaticResource WindowBackgroundBrush}"
BorderThickness="0"
Foreground="Transparent"
Maximum="1"
Value="{Binding ProgressPercent, Mode=OneWay}" />
<mahapps:MetroProgressBar
Grid.Column="0"
Grid.ColumnSpan="4"
Background="Transparent"
BorderThickness="0"
Foreground="{StaticResource PrimaryVariantBrush}"
Maximum="1"
Opacity="{Binding ProgressPercent, Mode=OneWay}"
Value="{Binding ProgressPercent, Mode=OneWay}" />
<TextBlock
Grid.Column="0"
Width="90"
Margin="10,0,0,8"
VerticalAlignment="Bottom"
FontFamily="Lucida Sans"
FontWeight="Black"
TextAlignment="Right">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="Configuring" />
<Setter Property="FontSize" Value="15" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding InstallingMode}" Value="True" />
<Condition Binding="{Binding Installing}" Value="True" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Text" Value="Installing" />
<Setter Property="FontSize" Value="14" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding InstallingMode}" Value="True" />
<Condition Binding="{Binding Installing}" Value="False" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Text" Value="Installed" />
<Setter Property="FontSize" Value="14" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock
Grid.Column="1"
Margin="15,0,0,0"
VerticalAlignment="Center"
FontFamily="Lucida Sans"
FontSize="25"
FontWeight="Black"
Text="{Binding ModListName}" />
<!-- Readd when Pause/Stop implementations added -->
<!--<Button Grid.Column="2"
ToolTip="Pause Installation"
Margin="0,0,0,5"
Width="50"
Visibility="{Binding InstallingMode, Converter={StaticResource bool2VisibilityConverter}}">
<icon:PackIconMaterial
Kind="Pause" />
</Button>
<Button Grid.Column="3"
ToolTip="Stop Installation"
Margin="0,0,0,5"
Width="50"
Visibility="{Binding InstallingMode, Converter={StaticResource bool2VisibilityConverter}}" >
<icon:PackIconFontAwesome
Width="25"
Height="25"
Kind="TimesCircleSolid" />
</Button>-->
</Grid>
<mahapps:MetroProgressBar
x:Name="BottomProgressBar"
Grid.Row="1"
Grid.RowSpan="2"
Height="5"
VerticalAlignment="Top"
Background="Transparent"
BorderBrush="Transparent"
Foreground="{StaticResource PrimaryBrush}"
Maximum="1"
Value="{Binding ProgressPercent, Mode=OneWay}" />
<mahapps:MetroProgressBar
x:Name="BottomProgressBarHighlight"
Grid.Row="1"
Grid.RowSpan="2"
Height="5"
VerticalAlignment="Top"
Background="Transparent"
BorderBrush="Transparent"
Maximum="1"
Value="{Binding ProgressPercent, Mode=OneWay}">
<mahapps:MetroProgressBar.Foreground>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="#CCFFFFFF" />
<GradientStop Offset="0.3" Color="#00FFFFFF" />
<GradientStop Offset="0.7" Color="#00FFFFFF" />
<GradientStop Offset="1" Color="#CCFFFFFF" />
</LinearGradientBrush>
</mahapps:MetroProgressBar.Foreground>
</mahapps:MetroProgressBar>
<!-- Bottom Area -->
<Grid
Grid.Row="3"
Margin="5,0,5,5"
ClipToBounds="True"
Visibility="{Binding InstallingMode, Converter={StaticResource bool2VisibilityConverter}, ConverterParameter=False}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="4" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button
Grid.Row="0"
Margin="30,5"
Command="{Binding OpenReadmeCommand}"
Content="Readme"
FontSize="20"
ToolTip="Open the readme for the modlist" />
<Button
Grid.Row="1"
Margin="30,5"
Command="{Binding VisitWebsiteCommand}"
Content="Website"
FontSize="20"
ToolTip="Open the webpage for the modlist" />
<Button
Grid.Row="2"
Margin="30,5"
Command="{Binding ShowReportCommand}"
Content="Manifest"
FontSize="20"
ToolTip="Open an explicit listing of all actions this modlist will take" />
</Grid>
<Grid
x:Name="InstallationConfigurationView"
Grid.Column="2"
Background="{StaticResource WindowBackgroundBrush}">
<Border
x:Name="ConfigurationBackgroundHaze"
Height="120"
Background="{StaticResource PrimaryVariantBrush}"
CornerRadius="50"
Opacity="0.10">
<Border.Effect>
<BlurEffect Radius="45" />
</Border.Effect>
</Border>
<ScrollViewer
Margin="5"
Background="Transparent"
VerticalScrollBarVisibility="Auto"
Visibility="{Binding InstallingMode, Converter={StaticResource bool2VisibilityConverter}, ConverterParameter=False}">
<Grid Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border
x:Name="BeginButtonPurpleGlow"
Grid.Row="1"
Grid.RowSpan="2"
Grid.Column="0"
Grid.ColumnSpan="5"
Width="76"
Height="76"
Margin="0,0,14,0"
HorizontalAlignment="Right"
Background="{StaticResource PrimaryVariantBrush}"
CornerRadius="43">
<Border.Effect>
<BlurEffect Radius="10" />
</Border.Effect>
<Border.Style>
<Style TargetType="Border">
<Setter Property="Opacity" Value="0.5" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, ElementName=BeginButton}" Value="True">
<Setter Property="Opacity" Value="0.8" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<TextBlock
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="14"
Text="Installation Location"
TextAlignment="Center" />
<local:FilePicker
Grid.Row="1"
Grid.Column="2"
Height="30"
VerticalAlignment="Center"
DoExistsCheck="False"
FontSize="14"
PathType="Folder"
PromptTitle="Select Installation directory"
TargetPath="{Binding Location}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="14"
Text="Download Location"
TextAlignment="Center" />
<local:FilePicker
Grid.Row="2"
Grid.Column="2"
Height="30"
VerticalAlignment="Center"
DoExistsCheck="False"
FontSize="14"
PathType="Folder"
PromptTitle="Select a location for MO2 downloads"
TargetPath="{Binding DownloadLocation}" />
<Button
x:Name="BeginButton"
Grid.Row="1"
Grid.RowSpan="2"
Grid.Column="4"
Width="55"
Height="55"
Margin="0,0,25,0"
HorizontalAlignment="Right"
Background="#222222"
BorderBrush="{StaticResource SecondaryBrush}"
Command="{Binding BeginCommand}"
Style="{StaticResource CircleButtonStyle}">
<icon:PackIconMaterial
Width="25"
Height="25"
Margin="5,0,0,0"
Kind="Play">
<icon:PackIconMaterial.Style>
<Style TargetType="icon:PackIconMaterial">
<Setter Property="Foreground" Value="{StaticResource SecondaryBrush}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, ElementName=BeginButton}" Value="True">
<Setter Property="Foreground" Value="#00ffe7" />
</DataTrigger>
</Style.Triggers>
</Style>
</icon:PackIconMaterial.Style>
</icon:PackIconMaterial>
<Button.Effect>
<DropShadowEffect
BlurRadius="15"
ShadowDepth="0"
Color="{StaticResource Secondary}" />
</Button.Effect>
</Button>
</Grid>
</ScrollViewer>
</Grid>
<Rectangle
Grid.Column="0"
Grid.ColumnSpan="3"
Height="1"
Margin="25,0"
VerticalAlignment="Top"
Fill="{StaticResource DarkBackgroundBrush}" />
</Grid>
<Grid
Grid.Row="3"
Margin="5,0,5,5"
Visibility="{Binding InstallingMode, Converter={StaticResource bool2VisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="4" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox
Grid.Column="0"
local:AutoScrollBehavior.ScrollOnNewItem="True"
ItemsSource="{Binding MWVM.Log}" />
<ListBox
x:Name="CpuListBox"
Grid.Column="2"
ItemsSource="{Binding MWVM.StatusList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ProgressBar
Grid.Column="0"
Width="100"
Maximum="100"
Minimum="0"
Value="{Binding Progress, Mode=OneTime}">
<ProgressBar.Style>
<Style TargetType="ProgressBar">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Progress, Mode=OneTime}" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ProgressBar.Style>
</ProgressBar>
<TextBlock Grid.Column="1" Text=" CPU " />
<TextBlock Grid.Column="2" Text="{Binding ID, Mode=OneTime}" />
<TextBlock Grid.Column="3" Text=" - " />
<TextBlock Grid.Column="4" Text="{Binding Msg, Mode=OneTime}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
</UserControl>

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Wabbajack
{
/// <summary>
/// Interaction logic for InstallationView.xaml
/// </summary>
public partial class InstallationView : UserControl
{
public InstallationView()
{
InitializeComponent();
}
}
}

View File

@ -0,0 +1,29 @@
<Window
x:Class="Wabbajack.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Wabbajack"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Wabbajack"
Width="1280"
Height="960"
MinWidth="850"
MinHeight="650"
Closing="Window_Closing"
Icon="../Resources/Icons/wabbajack.ico"
ResizeMode="CanResize"
Style="{StaticResource {x:Type Window}}"
WindowStyle="ToolWindow"
mc:Ignorable="d">
<ContentPresenter Content="{Binding ActivePane}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:CompilerVM}">
<local:CompilerView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:InstallerVM}">
<local:InstallationView />
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</Window>

View File

@ -0,0 +1,51 @@
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using Wabbajack.Common;
using Application = System.Windows.Application;
namespace Wabbajack
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MainWindowVM _mwvm;
public MainWindow()
{
string[] args = Environment.GetCommandLineArgs();
if (args.Length != 3) return;
var modlistPath = args[2];
Initialize(RunMode.Install, modlistPath);
}
public MainWindow(RunMode mode, string source)
{
Initialize(mode, source);
}
private void Initialize(RunMode mode, string source)
{
InitializeComponent();
_mwvm = new MainWindowVM(mode, source, this);
Utils.Log($"Wabbajack Build - {ThisAssembly.Git.Sha}");
this.DataContext = _mwvm;
}
internal bool ExitWhenClosing = true;
private void Window_Closing(object sender, CancelEventArgs e)
{
_mwvm.Dispose();
if (ExitWhenClosing)
{
Application.Current.Shutdown();
}
}
}
}

View File

@ -0,0 +1,139 @@
<Window
x:Class="Wabbajack.ModeSelectionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Wabbajack"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Wabbajack"
Width="1024"
Height="800"
Closing="Close_Window"
Icon="../Resources/Icons/wabbajack.ico"
ResizeMode="CanResize"
Style="{StaticResource {x:Type Window}}"
WindowStyle="ToolWindow"
mc:Ignorable="d">
<Grid Margin="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="150" />
<RowDefinition Height="*" />
<RowDefinition Height="70" />
<RowDefinition Height="70" />
<RowDefinition Height="70" />
</Grid.RowDefinitions>
<Image
Name="GitHub"
Grid.Row="0"
Grid.Column="0"
Margin="5,0,0,0"
HorizontalAlignment="Right"
MouseLeftButtonDown="GitHub_MouseLeftButtonDown" />
<Image
Name="Patreon"
Grid.Row="0"
Grid.Column="1"
Margin="5,0,0,0"
MouseLeftButtonDown="Patreon_MouseLeftButtonDown" />
<Image
Name="Discord"
Grid.Row="0"
Grid.Column="2"
Margin="5,0,0,0"
MouseLeftButtonDown="Discord_MouseLeftButtonDown" />
<Image
Name="Banner"
Grid.Row="1"
Grid.ColumnSpan="3"
Margin="2,0,2,0"
Stretch="Uniform" />
<ListBox
Grid.Row="2"
Grid.ColumnSpan="3"
ItemsSource="{Binding ModLists}"
ScrollViewer.CanContentScroll="False"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedItem="{Binding Path=SelectedModList, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="15" />
<RowDefinition Height="150" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Grid.RowSpan="4"
Grid.Column="0"
Source="{Binding Links.ImageUri}" />
<TextBlock
Grid.Row="0"
Grid.Column="2"
FontSize="20"
Text="{Binding Title}" />
<TextBlock
Grid.Row="1"
Grid.Column="2"
Text="{Binding Author}" />
<TextBlock
Grid.Row="1"
Grid.Column="4"
HorizontalAlignment="Right"
Text="{Binding Game}"
TextAlignment="Right" />
<TextBlock
Grid.Row="2"
Grid.Column="2"
Grid.ColumnSpan="3"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Text="{Binding Description}"
TextWrapping="Wrap" />
<Button Grid.Row="3" Grid.Column="2">More Info</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button
Name="InstallModlist"
Grid.Row="3"
Grid.ColumnSpan="3"
Margin="2"
Click="InstallModlist_Click"
IsEnabled="{Binding CanInstall}">
<TextBlock FontSize="40">Download and Install</TextBlock>
</Button>
<Button
Name="InstallFromList"
Grid.Row="4"
Grid.ColumnSpan="3"
Margin="2"
Click="InstallFromList_Click">
<TextBlock FontSize="40">Install from Disk</TextBlock>
</Button>
<Button
Name="CreateModlist"
Grid.Row="5"
Grid.ColumnSpan="3"
Margin="2"
Click="CreateModlist_Click">
<TextBlock FontSize="40">Create a ModList</TextBlock>
</Button>
</Grid>
</Window>

View File

@ -21,16 +21,16 @@ namespace Wabbajack
public ModeSelectionWindow() public ModeSelectionWindow()
{ {
InitializeComponent(); InitializeComponent();
var bannerImage = UIUtils.BitmapImageFromResource("Wabbajack.UI.banner_small_dark.png"); var bannerImage = UIUtils.BitmapImageFromResource("Wabbajack.Resources.banner_small_dark.png");
Banner.Source = bannerImage; Banner.Source = bannerImage;
var patreonIcon = UIUtils.BitmapImageFromResource("Wabbajack.UI.Icons.patreon_light.png"); var patreonIcon = UIUtils.BitmapImageFromResource("Wabbajack.Resources.Icons.patreon_light.png");
Patreon.Source = patreonIcon; Patreon.Source = patreonIcon;
var githubIcon = UIUtils.BitmapImageFromResource("Wabbajack.UI.Icons.github_light.png"); var githubIcon = UIUtils.BitmapImageFromResource("Wabbajack.Resources.Icons.github_light.png");
GitHub.Source = githubIcon; GitHub.Source = githubIcon;
var discordIcon = UIUtils.BitmapImageFromResource("Wabbajack.UI.Icons.discord.png"); var discordIcon = UIUtils.BitmapImageFromResource("Wabbajack.Resources.Icons.discord.png");
Discord.Source = discordIcon; Discord.Source = discordIcon;
DataContext = new ModeSelectionWindowViewModel(); DataContext = new ModeSelectionWindowVM();
} }
private void CreateModlist_Click(object sender, RoutedEventArgs e) private void CreateModlist_Click(object sender, RoutedEventArgs e)
@ -46,7 +46,7 @@ namespace Wabbajack
// RunMode.Install, // RunMode.Install,
// UIUtils.OpenFileDialog($"Wabbajack Modlist (*{Consts.ModlistExtension})|*{Consts.ModlistExtension}")); // UIUtils.OpenFileDialog($"Wabbajack Modlist (*{Consts.ModlistExtension})|*{Consts.ModlistExtension}"));
var result = ((ModeSelectionWindowViewModel)DataContext).Download(); var result = ((ModeSelectionWindowVM)DataContext).Download();
if (result != null) if (result != null)
{ {
OpenMainWindow(RunMode.Install, result); OpenMainWindow(RunMode.Install, result);

View File

@ -0,0 +1,21 @@
<Window
x:Class="Wabbajack.TextViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Wabbajack"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="TextViewer"
Width="800"
Height="450"
Icon="../Resources/Icons/wabbajack.ico"
Style="{StaticResource {x:Type Window}}"
WindowStyle="ToolWindow"
mc:Ignorable="d">
<Grid>
<TextBlock
Name="TextBlock"
FontSize="20"
TextWrapping="Wrap" />
</Grid>
</Window>

View File

@ -1,6 +1,6 @@
using System.Windows; using System.Windows;
namespace Wabbajack.UI namespace Wabbajack
{ {
public partial class TextViewer : Window public partial class TextViewer : Window
{ {

View File

@ -0,0 +1,48 @@
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace Wabbajack
{
public class UserControlRx : UserControl, IDisposable, IReactiveObject
{
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangingEventHandler PropertyChanging;
public void RaisePropertyChanging(PropertyChangingEventArgs args)
{
PropertyChanging?.Invoke(this, args);
}
public void RaisePropertyChanged(PropertyChangedEventArgs args)
{
PropertyChanged?.Invoke(this, args);
}
private readonly Lazy<CompositeDisposable> _CompositeDisposable = new Lazy<CompositeDisposable>();
public CompositeDisposable CompositeDisposable => _CompositeDisposable.Value;
public virtual void Dispose()
{
if (_CompositeDisposable.IsValueCreated)
{
_CompositeDisposable.Value.Dispose();
}
}
protected static void WireNotifyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is UserControlRx control)) return;
if (object.Equals(e.OldValue, e.NewValue)) return;
control.RaisePropertyChanged(e.Property.Name);
}
}
}

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Costura.Fody.4.0.0\build\Costura.Fody.props" Condition="Exists('..\packages\Costura.Fody.4.0.0\build\Costura.Fody.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@ -32,6 +31,7 @@
<ApplicationVersion>1.0.0.%2a</ApplicationVersion> <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust> <UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled> <BootstrapperEnabled>true</BootstrapperEnabled>
<XamlDebuggingInformation>True</XamlDebuggingInformation>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
@ -54,7 +54,7 @@
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<ApplicationIcon>UI\Icons\wabbajack.ico</ApplicationIcon> <ApplicationIcon>Resources\Icons\wabbajack.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug %28no commandargs%29|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug %28no commandargs%29|AnyCPU'">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@ -96,107 +96,18 @@
<Prefer32Bit>true</Prefer32Bit> <Prefer32Bit>true</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="AlphaFS, Version=2.2.0.0, Culture=neutral, PublicKeyToken=4d31a58f7d7ad5c9, processorArchitecture=MSIL">
<HintPath>..\packages\AlphaFS.2.2.6\lib\net452\AlphaFS.dll</HintPath>
</Reference>
<Reference Include="CommonMark, Version=0.1.0.0, Culture=neutral, PublicKeyToken=001ef8810438905d, processorArchitecture=MSIL">
<HintPath>..\packages\CommonMark.NET.0.15.1\lib\net45\CommonMark.dll</HintPath>
</Reference>
<Reference Include="Costura, Version=4.0.0.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL">
<HintPath>..\packages\Costura.Fody.4.0.0\lib\net40\Costura.dll</HintPath>
</Reference>
<Reference Include="DynamicData, Version=6.13.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DynamicData.6.13.17\lib\net461\DynamicData.dll</HintPath>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib, Version=1.2.0.246, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<HintPath>..\packages\SharpZipLib.1.2.0\lib\net45\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="K4os.Compression.LZ4, Version=1.1.11.0, Culture=neutral, PublicKeyToken=2186fa9121ef231d, processorArchitecture=MSIL">
<HintPath>..\packages\K4os.Compression.LZ4.1.1.11\lib\net46\K4os.Compression.LZ4.dll</HintPath>
</Reference>
<Reference Include="K4os.Compression.LZ4.Streams, Version=1.1.11.0, Culture=neutral, PublicKeyToken=2186fa9121ef231d, processorArchitecture=MSIL">
<HintPath>..\packages\K4os.Compression.LZ4.Streams.1.1.11\lib\net46\K4os.Compression.LZ4.Streams.dll</HintPath>
</Reference>
<Reference Include="K4os.Hash.xxHash, Version=1.0.6.0, Culture=neutral, PublicKeyToken=32cd54395057cec3, processorArchitecture=MSIL">
<HintPath>..\packages\K4os.Hash.xxHash.1.0.6\lib\net46\K4os.Hash.xxHash.dll</HintPath>
</Reference>
<Reference Include="MegaApiClient, Version=1.7.1.495, Culture=neutral, PublicKeyToken=0480d311efbeb4e2, processorArchitecture=MSIL">
<HintPath>..\packages\MegaApiClient.1.7.1\lib\net46\MegaApiClient.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsAPICodePack, Version=1.1.3.3, Culture=neutral, PublicKeyToken=8985beaab7ea3f04, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft-WindowsAPICodePack-Core.1.1.3.3\lib\net452\Microsoft.WindowsAPICodePack.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsAPICodePack.Shell, Version=1.1.3.3, Culture=neutral, PublicKeyToken=8985beaab7ea3f04, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft-WindowsAPICodePack-Shell.1.1.3.3\lib\net452\Microsoft.WindowsAPICodePack.Shell.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json.Bson, Version=1.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.Bson.1.0.2\lib\net45\Newtonsoft.Json.Bson.dll</HintPath>
</Reference>
<Reference Include="Pharmacist.Common, Version=1.2.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Pharmacist.Common.1.2.2\lib\netstandard2.0\Pharmacist.Common.dll</HintPath>
</Reference>
<Reference Include="PresentationFramework.Aero" /> <Reference Include="PresentationFramework.Aero" />
<Reference Include="ReactiveUI, Version=10.4.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ReactiveUI.10.4.1\lib\net461\ReactiveUI.dll</HintPath>
</Reference>
<Reference Include="ReactiveUI.Events.WPF, Version=10.4.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ReactiveUI.Events.WPF.10.4.1\lib\net461\ReactiveUI.Events.WPF.dll</HintPath>
</Reference>
<Reference Include="ReactiveUI.WPF, Version=10.4.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ReactiveUI.WPF.10.4.1\lib\net461\ReactiveUI.WPF.dll</HintPath>
</Reference>
<Reference Include="SharpCompress, Version=0.23.0.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL">
<HintPath>..\packages\SharpCompress.0.23.0\lib\net45\SharpCompress.dll</HintPath>
</Reference>
<Reference Include="Syroot.KnownFolders, Version=1.2.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Syroot.Windows.IO.KnownFolders.1.2.1\lib\net452\Syroot.KnownFolders.dll</HintPath>
</Reference>
<Reference Include="Splat, Version=9.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Splat.9.1.1\lib\net461\Splat.dll</HintPath>
</Reference>
<Reference Include="Splat.Drawing, Version=9.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Splat.Drawing.9.1.1\lib\net461\Splat.Drawing.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Configuration" /> <Reference Include="System.Configuration" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Design" /> <Reference Include="System.Design" />
<Reference Include="System.Drawing" /> <Reference Include="System.Drawing" />
<Reference Include="System.Drawing.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Drawing.Primitives.4.3.0\lib\net45\System.Drawing.Primitives.dll</HintPath>
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.IO.Compression" /> <Reference Include="System.IO.Compression" />
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" /> <Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Reactive, Version=4.2.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reactive.4.2.0\lib\net46\System.Reactive.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" /> <Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Security" /> <Reference Include="System.Security" />
<Reference Include="System.ServiceModel" /> <Reference Include="System.ServiceModel" />
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.3\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Transactions" /> <Reference Include="System.Transactions" />
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Web" /> <Reference Include="System.Web" />
<Reference Include="System.Windows" /> <Reference Include="System.Windows" />
<Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Forms" />
@ -209,53 +120,63 @@
<Reference Include="System.Xaml"> <Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework> <RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference> </Reference>
<Reference Include="websocket-sharp, Version=1.0.4.0, Culture=neutral, PublicKeyToken=5660b08a1845a91e, processorArchitecture=MSIL">
<HintPath>..\packages\WebSocketSharpFork.1.0.4.0\lib\net35\websocket-sharp.dll</HintPath>
</Reference>
<Reference Include="WindowsBase" /> <Reference Include="WindowsBase" />
<Reference Include="PresentationCore" /> <Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" /> <Reference Include="PresentationFramework" />
<Reference Include="YamlDotNet, Version=8.0.0.0, Culture=neutral, PublicKeyToken=ec19458f3c15af5e, processorArchitecture=MSIL">
<HintPath>..\packages\YamlDotNet.8.0.0\lib\net45\YamlDotNet.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ApplicationDefinition Include="App.xaml"> <ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</ApplicationDefinition> </ApplicationDefinition>
<Compile Include="Converters\BoolToVisibilityConverter.cs" />
<Compile Include="Views\CompilerView.xaml.cs">
<DependentUpon>CompilerView.xaml</DependentUpon>
</Compile>
<Compile Include="Converters\IsNotNullVisibilityConverter.cs" /> <Compile Include="Converters\IsNotNullVisibilityConverter.cs" />
<Compile Include="Enums\RunMode.cs" /> <Compile Include="Enums\RunMode.cs" />
<Compile Include="Extensions\ReactiveUIExt.cs" /> <Compile Include="Extensions\ReactiveUIExt.cs" />
<Compile Include="UI\DownloadWindow.xaml.cs"> <Compile Include="Views\DownloadWindow.xaml.cs">
<DependentUpon>DownloadWindow.xaml</DependentUpon> <DependentUpon>DownloadWindow.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="UI\ModeSelectionWindowViewModel.cs" /> <Compile Include="View Models\ModeSelectionWindowVM.cs" />
<Compile Include="UI\ModListDefinition.cs" /> <Compile Include="View Models\ModListDefinition.cs" />
<Compile Include="UI\SlideshowView.xaml.cs"> <Compile Include="Views\FilePicker.xaml.cs">
<DependentUpon>SlideshowView.xaml</DependentUpon> <DependentUpon>FilePicker.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="UI\ModlistPropertiesWindow.xaml.cs"> <Compile Include="Views\InstallationView.xaml.cs">
<DependentUpon>ModlistPropertiesWindow.xaml</DependentUpon> <DependentUpon>InstallationView.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="UI\ModeSelectionWindow.xaml.cs"> <Compile Include="Util\CPUStatus.cs" />
<Compile Include="View Models\CompilerVM.cs" />
<Compile Include="View Models\MainWindowVM.cs" />
<Compile Include="Views\ModeSelectionWindow.xaml.cs">
<DependentUpon>ModeSelectionWindow.xaml</DependentUpon> <DependentUpon>ModeSelectionWindow.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Themes\LeftMarginMultiplierConverter.cs" /> <Compile Include="Converters\LeftMarginMultiplierConverter.cs" />
<Compile Include="Themes\TreeViewItemExtensions.cs" /> <Compile Include="Util\TreeViewItemExtensions.cs" />
<Compile Include="UI\SlideShow.cs" /> <Compile Include="View Models\SlideShow.cs" />
<Compile Include="UI\TextViewer.xaml.cs"> <Compile Include="Views\TextViewer.xaml.cs">
<DependentUpon>TextViewer.xaml</DependentUpon> <DependentUpon>TextViewer.xaml</DependentUpon>
</Compile> </Compile>
<Page Include="UI\DownloadWindow.xaml"> <Compile Include="Views\UserControlRx.cs" />
<Page Include="Views\CompilerView.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="UI\SlideshowView.xaml"> <Page Include="Views\DownloadWindow.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="UI\MainWindow.xaml"> <Page Include="Views\FilePicker.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\InstallationView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\MainWindow.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
@ -263,17 +184,13 @@
<DependentUpon>App.xaml</DependentUpon> <DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="AppState.cs" /> <Compile Include="View Models\InstallerVM.cs" />
<Compile Include="AutoScrollBehavior.cs" /> <Compile Include="Util\AutoScrollBehavior.cs" />
<Compile Include="UI\MainWindow.xaml.cs"> <Compile Include="Views\MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon> <DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Page Include="UI\ModeSelectionWindow.xaml"> <Page Include="Views\ModeSelectionWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="UI\ModlistPropertiesWindow.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
@ -281,9 +198,9 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Include="UI\TextViewer.xaml"> <Page Include="Views\TextViewer.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page> </Page>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -304,7 +221,6 @@
<Generator>ResXFileCodeGenerator</Generator> <Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput> <LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource> </EmbeddedResource>
<None Include="packages.config" />
<None Include="Properties\Settings.settings"> <None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator> <Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput> <LastGenOutput>Settings.Designer.cs</LastGenOutput>
@ -344,44 +260,112 @@
</BootstrapperPackage> </BootstrapperPackage>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="UI\banner.png" /> <Resource Include="Resources\Icons\wabbajack.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="UI\banner_small.png" /> <EmbeddedResource Include="Resources\banner.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="UI\Icons\discord.png" /> <EmbeddedResource Include="Resources\banner_small.png" />
<EmbeddedResource Include="UI\Icons\github.png" />
<EmbeddedResource Include="UI\Icons\patreon.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="UI\Icons\next.png" /> <EmbeddedResource Include="Resources\Icons\discord.png" />
<EmbeddedResource Include="Resources\Icons\github.png" />
<EmbeddedResource Include="Resources\Icons\patreon.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="UI\none.jpg" /> <EmbeddedResource Include="Resources\Icons\next.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="UI\Icons\github_light.png" /> <EmbeddedResource Include="Resources\none.jpg" />
<EmbeddedResource Include="UI\Icons\patreon_light.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="UI\Banner_Dark.png" /> <EmbeddedResource Include="Resources\Icons\github_light.png" />
<EmbeddedResource Include="Resources\Icons\patreon_light.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="UI\banner_small_dark.png" /> <EmbeddedResource Include="Resources\Banner_Dark.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Resource Include="UI\Icons\wabbajack.ico" /> <EmbeddedResource Include="Resources\banner_small_dark.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AlphaFS">
<Version>2.2.6</Version>
</PackageReference>
<PackageReference Include="CommonMark.NET">
<Version>0.15.1</Version>
</PackageReference>
<PackageReference Include="Costura.Fody">
<Version>4.0.0</Version>
</PackageReference>
<PackageReference Include="DynamicData">
<Version>6.13.17</Version>
</PackageReference>
<PackageReference Include="Fody">
<Version>5.1.1</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="GitInfo">
<Version>2.0.20</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="K4os.Compression.LZ4.Streams">
<Version>1.1.11</Version>
</PackageReference>
<PackageReference Include="MahApps.Metro">
<Version>1.6.5</Version>
</PackageReference>
<PackageReference Include="MahApps.Metro.IconPacks">
<Version>2.3.0</Version>
</PackageReference>
<PackageReference Include="MegaApiClient">
<Version>1.7.1</Version>
</PackageReference>
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell">
<Version>1.1.3.3</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>12.0.2</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json.Bson">
<Version>1.0.2</Version>
</PackageReference>
<PackageReference Include="protobuf-net">
<Version>2.4.0</Version>
</PackageReference>
<PackageReference Include="ReactiveUI.Events.WPF">
<Version>10.4.1</Version>
</PackageReference>
<PackageReference Include="ReactiveUI.WPF">
<Version>10.4.1</Version>
</PackageReference>
<PackageReference Include="SharpCompress">
<Version>0.23.0</Version>
</PackageReference>
<PackageReference Include="SharpZipLib">
<Version>1.2.0</Version>
</PackageReference>
<PackageReference Include="Syroot.Windows.IO.KnownFolders">
<Version>1.2.1</Version>
</PackageReference>
<PackageReference Include="System.Reactive">
<Version>4.2.0</Version>
</PackageReference>
<PackageReference Include="WebSocketSharpFork">
<Version>1.0.4</Version>
</PackageReference>
<PackageReference Include="WPFThemes.DarkBlend">
<Version>1.0.8</Version>
</PackageReference>
<PackageReference Include="YamlDotNet">
<Version>7.0.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Wabba_Mouth.png" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Fody.5.1.1\build\Fody.targets" Condition="Exists('..\packages\Fody.5.1.1\build\Fody.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Fody.5.1.1\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.5.1.1\build\Fody.targets'))" />
<Error Condition="!Exists('..\packages\Costura.Fody.4.0.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.4.0.0\build\Costura.Fody.props'))" />
<Error Condition="!Exists('..\packages\GitInfo.2.0.20\build\GitInfo.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\GitInfo.2.0.20\build\GitInfo.targets'))" />
</Target>
<Import Project="..\packages\GitInfo.2.0.20\build\GitInfo.targets" Condition="Exists('..\packages\GitInfo.2.0.20\build\GitInfo.targets')" />
</Project> </Project>

View File

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AlphaFS" version="2.2.6" targetFramework="net472" />
<package id="CommonMark.NET" version="0.15.1" targetFramework="net472" />
<package id="Costura.Fody" version="4.0.0" targetFramework="net472" />
<package id="DynamicData" version="6.13.17" targetFramework="net472" />
<package id="Fody" version="5.1.1" targetFramework="net472" developmentDependency="true" />
<package id="GitInfo" version="2.0.20" targetFramework="net472" developmentDependency="true" />
<package id="K4os.Compression.LZ4" version="1.1.11" targetFramework="net472" />
<package id="K4os.Compression.LZ4.Streams" version="1.1.11" targetFramework="net472" />
<package id="K4os.Hash.xxHash" version="1.0.6" targetFramework="net472" />
<package id="MegaApiClient" version="1.7.1" targetFramework="net472" />
<package id="Microsoft-WindowsAPICodePack-Core" version="1.1.3.3" targetFramework="net472" />
<package id="Microsoft-WindowsAPICodePack-Shell" version="1.1.3.3" targetFramework="net472" />
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net472" />
<package id="Newtonsoft.Json.Bson" version="1.0.2" targetFramework="net472" />
<package id="Pharmacist.Common" version="1.2.2" targetFramework="net472" />
<package id="protobuf-net" version="2.4.0" targetFramework="net472" />
<package id="ReactiveUI" version="10.4.1" targetFramework="net472" />
<package id="ReactiveUI.Events.WPF" version="10.4.1" targetFramework="net472" />
<package id="ReactiveUI.WPF" version="10.4.1" targetFramework="net472" />
<package id="SharpCompress" version="0.23.0" targetFramework="net472" />
<package id="SharpZipLib" version="1.2.0" targetFramework="net472" />
<package id="Splat" version="9.1.1" targetFramework="net472" />
<package id="Splat.Drawing" version="9.1.1" targetFramework="net472" />
<package id="Syroot.Windows.IO.KnownFolders" version="1.2.1" targetFramework="net472" />
<package id="System.Buffers" version="4.4.0" targetFramework="net472" />
<package id="System.Drawing.Primitives" version="4.3.0" targetFramework="net472" />
<package id="System.Memory" version="4.5.3" targetFramework="net472" />
<package id="System.Numerics.Vectors" version="4.4.0" targetFramework="net472" />
<package id="System.Reactive" version="4.2.0" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.3" targetFramework="net472" />
<package id="System.ValueTuple" version="4.5.0" targetFramework="net472" />
<package id="WebSocketSharpFork" version="1.0.4.0" targetFramework="net472" />
<package id="WPFThemes.DarkBlend" version="1.0.8" targetFramework="net472" />
<package id="YamlDotNet" version="8.0.0" targetFramework="net472" />
</packages>