Rework gallery loading, and VM creation

This commit is contained in:
Timothy Baldridge 2021-12-29 10:26:12 -07:00
parent 12bb5fe083
commit bd71de5883
24 changed files with 293 additions and 205 deletions

View File

@ -37,6 +37,8 @@ namespace Wabbajack
services.AddSingleton<MainSettings>(); services.AddSingleton<MainSettings>();
services.AddTransient<CompilerVM>(); services.AddTransient<CompilerVM>();
services.AddTransient<InstallerVM>(); services.AddTransient<InstallerVM>();
services.AddTransient<ModeSelectionVM>();
services.AddTransient<ModListGalleryVM>();
return services; return services;

View File

@ -0,0 +1,11 @@
using ReactiveUI;
namespace Wabbajack.Messages;
public class NavigateBack
{
public static void Send()
{
MessageBus.Current.SendMessage(new NavigateBack());
}
}

View File

@ -0,0 +1,21 @@
using ReactiveUI;
using Wabbajack.Lib;
namespace Wabbajack.Messages;
public class NavigateTo
{
public ViewModel ViewModel { get; }
private NavigateTo(ViewModel vm)
{
ViewModel = vm;
}
public static void Send<T>(T vm)
where T : ViewModel
{
MessageBus.Current.SendMessage(new NavigateTo(vm));
}
}

View File

@ -0,0 +1,29 @@
using ReactiveUI;
namespace Wabbajack.Messages;
public class NavigateToGlobal
{
public enum ScreenType
{
ModeSelectionView,
ModListGallery,
Installer,
Settings,
Compiler,
ModListContents
}
public ScreenType Screen { get; }
private NavigateToGlobal(ScreenType screen)
{
Screen = screen;
}
public static void Send(ScreenType screen)
{
MessageBus.Current.SendMessage(new NavigateToGlobal(screen));
}
}

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Reactive; using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -7,6 +8,7 @@ using ReactiveUI;
using ReactiveUI.Fody.Helpers; using ReactiveUI.Fody.Helpers;
using Wabbajack.Common; using Wabbajack.Common;
using Wabbajack.Lib; using Wabbajack.Lib;
using Wabbajack.Messages;
namespace Wabbajack namespace Wabbajack
{ {
@ -24,27 +26,30 @@ namespace Wabbajack
[Reactive] [Reactive]
public ViewModel NavigateBackTarget { get; set; } public ViewModel NavigateBackTarget { get; set; }
public ReactiveCommand<Unit, Unit> BackCommand { get; protected set; } public ReactiveCommand<Unit, Unit> BackCommand { get; protected set; }
protected ObservableAsPropertyHelper<bool> _IsActive; [Reactive]
public bool IsActive => _IsActive.Value; public bool IsActive { get; set; }
public Subject<bool> IsBackEnabledSubject { get; } = new Subject<bool>(); public Subject<bool> IsBackEnabledSubject { get; } = new Subject<bool>();
public IObservable<bool> IsBackEnabled { get; } public IObservable<bool> IsBackEnabled { get; }
public BackNavigatingVM(ILogger logger, MainWindowVM mainWindowVM) public BackNavigatingVM(ILogger logger)
{ {
IsBackEnabled = IsBackEnabledSubject.StartWith(true); IsBackEnabled = IsBackEnabledSubject.StartWith(true);
BackCommand = ReactiveCommand.Create( BackCommand = ReactiveCommand.Create(
execute: () => logger.CatchAndLog(() => execute: () => logger.CatchAndLog(() =>
{ {
mainWindowVM.NavigateTo(NavigateBackTarget); NavigateBack.Send();
Unload(); Unload();
}), }),
canExecute: this.ConstructCanNavigateBack() canExecute: this.ConstructCanNavigateBack()
.ObserveOnGuiThread()); .ObserveOnGuiThread());
_IsActive = this.ConstructIsActive(mainWindowVM) this.WhenActivated(disposables =>
.ToGuiProperty(this, nameof(IsActive)); {
IsActive = true;
Disposable.Create(() => IsActive = false).DisposeWith(disposables);
});
} }
public virtual void Unload() public virtual void Unload()
@ -60,7 +65,7 @@ namespace Wabbajack
.CombineLatest(vm.IsBackEnabled) .CombineLatest(vm.IsBackEnabled)
.Select(x => x.First != null && x.Second); .Select(x => x.First != null && x.Second);
} }
public static IObservable<bool> ConstructIsActive(this IBackNavigatingVM vm, MainWindowVM mwvm) public static IObservable<bool> ConstructIsActive(this IBackNavigatingVM vm, MainWindowVM mwvm)
{ {
return mwvm.WhenAny(x => x.ActivePane) return mwvm.WhenAny(x => x.ActivePane)

View File

@ -13,6 +13,7 @@ using Microsoft.Extensions.Logging;
using Wabbajack.Compiler; using Wabbajack.Compiler;
using Wabbajack.Lib.Extensions; using Wabbajack.Lib.Extensions;
using Wabbajack.Lib.Interventions; using Wabbajack.Lib.Interventions;
using Wabbajack.Messages;
using Wabbajack.RateLimiter; using Wabbajack.RateLimiter;
namespace Wabbajack namespace Wabbajack
@ -64,7 +65,7 @@ namespace Wabbajack
private readonly ObservableAsPropertyHelper<(int CurrentCPUs, int DesiredCPUs)> _CurrentCpuCount; private readonly ObservableAsPropertyHelper<(int CurrentCPUs, int DesiredCPUs)> _CurrentCpuCount;
public (int CurrentCPUs, int DesiredCPUs) CurrentCpuCount => _CurrentCpuCount.Value; public (int CurrentCPUs, int DesiredCPUs) CurrentCpuCount => _CurrentCpuCount.Value;
public CompilerVM(ILogger<CompilerVM> logger, MainWindowVM mainWindowVM) : base(logger, mainWindowVM) public CompilerVM(ILogger<CompilerVM> logger, MainWindowVM mainWindowVM) : base(logger)
{ {
MWVM = mainWindowVM; MWVM = mainWindowVM;
@ -133,7 +134,7 @@ namespace Wabbajack
BackCommand = ReactiveCommand.Create( BackCommand = ReactiveCommand.Create(
execute: () => execute: () =>
{ {
mainWindowVM.NavigateTo(mainWindowVM.ModeSelectionVM); NavigateToGlobal.Send(NavigateToGlobal.ScreenType.ModeSelectionView);
StartedCompilation = false; StartedCompilation = false;
Completed = null; Completed = null;
}, },

View File

@ -1,9 +1,13 @@
using System; 
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Reactive; using System.Reactive;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
using DynamicData; using DynamicData;
using DynamicData.Binding; using DynamicData.Binding;
@ -17,6 +21,7 @@ using Wabbajack.DTOs;
using Wabbajack.Hashing.xxHash64; using Wabbajack.Hashing.xxHash64;
using Wabbajack.Lib.Extensions; using Wabbajack.Lib.Extensions;
using Wabbajack.Networking.WabbajackClientApi; using Wabbajack.Networking.WabbajackClientApi;
using Wabbajack.Services.OSIntegrated;
using Wabbajack.Services.OSIntegrated.Services; using Wabbajack.Services.OSIntegrated.Services;
namespace Wabbajack namespace Wabbajack
@ -24,68 +29,48 @@ namespace Wabbajack
public class ModListGalleryVM : BackNavigatingVM public class ModListGalleryVM : BackNavigatingVM
{ {
public MainWindowVM MWVM { get; } public MainWindowVM MWVM { get; }
public ObservableCollectionExtended<ModListMetadataVM> ModLists { get; } = new ObservableCollectionExtended<ModListMetadataVM>(); private readonly SourceCache<ModListMetadataVM, string> _modLists = new(x => x.Metadata.Links.MachineURL);
public ReadOnlyObservableCollection<ModListMetadataVM> _filteredModLists;
public ReadOnlyObservableCollection<ModListMetadataVM> ModLists => _filteredModLists;
private const string ALL_GAME_TYPE = "All"; private const string ALL_GAME_TYPE = "All";
[Reactive] [Reactive] public IErrorResponse Error { get; set; }
public IErrorResponse Error { get; set; }
[Reactive] [Reactive] public string Search { get; set; }
public string Search { get; set; }
[Reactive] [Reactive] public bool OnlyInstalled { get; set; }
public bool OnlyInstalled { get; set; }
[Reactive] [Reactive] public bool ShowNSFW { get; set; }
public bool ShowNSFW { get; set; }
[Reactive] [Reactive] public bool ShowUtilityLists { get; set; }
public bool ShowUtilityLists { get; set; }
[Reactive] [Reactive] public string GameType { get; set; }
public string GameType { get; set; }
public List<string> GameTypeEntries { get { return GetGameTypeEntries(); } } public List<string> GameTypeEntries => GetGameTypeEntries();
private readonly ObservableAsPropertyHelper<bool> _Loaded; private ObservableAsPropertyHelper<bool> _Loaded;
private readonly Client _wjClient; private readonly Client _wjClient;
private readonly ILogger<ModListGalleryVM> _logger; private readonly ILogger<ModListGalleryVM> _logger;
private readonly GameLocator _locator; private readonly GameLocator _locator;
private readonly ModListDownloadMaintainer _maintainer;
private FiltersSettings settings => MWVM.Settings.Filters; private FiltersSettings settings { get; set; } = new();
public bool Loaded => _Loaded.Value; public bool Loaded => _Loaded.Value;
public ICommand ClearFiltersCommand { get; } public ICommand ClearFiltersCommand { get; set; }
public ModListGalleryVM(ILogger<ModListGalleryVM> logger, MainWindowVM mainWindowVM, Client wjClient, public ModListGalleryVM(ILogger<ModListGalleryVM> logger, Client wjClient,
GameLocator locator, IServiceProvider provider) GameLocator locator, SettingsManager settingsManager, ModListDownloadMaintainer maintainer)
: base(logger, mainWindowVM) : base(logger)
{ {
MWVM = mainWindowVM;
_wjClient = wjClient; _wjClient = wjClient;
_logger = logger; _logger = logger;
_locator = locator; _locator = locator;
_maintainer = maintainer;
// load persistent filter settings
if (settings.IsPersistent)
{
GameType = !string.IsNullOrEmpty(settings.Game) ? settings.Game : ALL_GAME_TYPE;
ShowNSFW = settings.ShowNSFW;
ShowUtilityLists = settings.ShowUtilityLists;
OnlyInstalled = settings.OnlyInstalled;
Search = settings.Search;
}
else
GameType = ALL_GAME_TYPE;
// subscribe to save signal
MWVM.Settings.SaveSignal
.Subscribe(_ => UpdateFiltersSettings())
.DisposeWith(this.CompositeDisposable);
ClearFiltersCommand = ReactiveCommand.Create( ClearFiltersCommand = ReactiveCommand.Create(
() => () =>
{ {
@ -97,101 +82,16 @@ namespace Wabbajack
}); });
this.WhenAny(x => x.OnlyInstalled) this.WhenActivated(disposables =>
.Subscribe(val => {
{ var _ = LoadModLists();
if(val)
GameType = ALL_GAME_TYPE; _modLists.Connect()
}) .ObserveOn(RxApp.MainThreadScheduler)
.DisposeWith(CompositeDisposable); .Bind(out _filteredModLists)
.Subscribe()
var sourceList = Observable.Return(Unit.Default) .DisposeWith(disposables);
.ObserveOn(RxApp.TaskpoolScheduler) });
.SelectMany(async _ =>
{
try
{
Error = null;
var list = await _wjClient.LoadLists();
Error = ErrorResponse.Success;
return list
.AsObservableChangeSet(x => x.DownloadMetadata?.Hash ?? default);
}
catch (Exception ex)
{
_logger.LogError(ex, "While Loading Lists");
Error = ErrorResponse.Fail(ex);
return Observable.Empty<IChangeSet<ModlistMetadata, Hash>>();
}
})
// Unsubscribe and release when not active
.FlowSwitch(
this.WhenAny(x => x.IsActive),
valueWhenOff: Observable.Return(ChangeSet<ModlistMetadata, Hash>.Empty))
.Switch()
.RefCount();
_Loaded = sourceList.CollectionCount()
.Select(c => c > 0)
.ToProperty(this, nameof(Loaded));
// Convert to VM and bind to resulting list
sourceList
.ObserveOnGuiThread()
.Transform(m => new ModListMetadataVM(provider.GetRequiredService<ILogger<ModListMetadataVM>>(),this, m,
provider.GetRequiredService<ModListDownloadMaintainer>(), provider.GetRequiredService<Client>()))
.DisposeMany()
// Filter only installed
.Filter(this.WhenAny(x => x.OnlyInstalled)
.Select<bool, Func<ModListMetadataVM, bool>>(onlyInstalled => (vm) =>
{
if (!onlyInstalled) return true;
if (!GameRegistry.Games.TryGetValue(vm.Metadata.Game, out var gameMeta)) return false;
return _locator.IsInstalled(gameMeta.Game);
}))
// Filter on search box
.Filter(this.WhenAny(x => x.Search)
.Debounce(TimeSpan.FromMilliseconds(150), RxApp.MainThreadScheduler)
.Select<string, Func<ModListMetadataVM, bool>>(search => (vm) =>
{
if (string.IsNullOrWhiteSpace(search)) return true;
return vm.Metadata.Title.ContainsCaseInsensitive(search) || vm.Metadata.tags.Any(t => t.ContainsCaseInsensitive(search));
}))
.Filter(this.WhenAny(x => x.ShowNSFW)
.Select<bool, Func<ModListMetadataVM, bool>>(showNSFW => vm =>
{
if (!vm.Metadata.NSFW) return true;
return vm.Metadata.NSFW && showNSFW;
}))
.Filter(this.WhenAny(x => x.ShowUtilityLists)
.Select<bool, Func<ModListMetadataVM, bool>>(showUtilityLists => vm => showUtilityLists ? vm.Metadata.UtilityList : !vm.Metadata.UtilityList))
// Filter by Game
.Filter(this.WhenAny(x => x.GameType)
.Debounce(TimeSpan.FromMilliseconds(150), RxApp.MainThreadScheduler)
.Select<string, Func<ModListMetadataVM, bool>>(GameType => (vm) =>
{
if (GameType == ALL_GAME_TYPE)
return true;
if (string.IsNullOrEmpty(GameType))
return false;
return GameType == vm.Metadata.Game.GetDescription<Game>().ToString();
}))
.Bind(ModLists)
.Subscribe()
.DisposeWith(CompositeDisposable);
// Extra GC when navigating away, just to immediately clean up modlist metadata
this.WhenAny(x => x.IsActive)
.Where(x => !x)
.Skip(1)
.Delay(TimeSpan.FromMilliseconds(50), RxApp.MainThreadScheduler)
.Subscribe(_ =>
{
GC.Collect();
})
.DisposeWith(CompositeDisposable);
} }
public override void Unload() public override void Unload()
@ -199,9 +99,30 @@ namespace Wabbajack
Error = null; Error = null;
} }
private async Task LoadModLists()
{
using var ll = LoadingLock.WithLoading();
try
{
var modLists = await _wjClient.LoadLists();
_modLists.Edit(e =>
{
e.Clear();
e.AddOrUpdate(modLists.Select(m =>
new ModListMetadataVM(_logger, this, m, _maintainer, _wjClient)));
});
}
catch (Exception ex)
{
_logger.LogError(ex, "While loading lists");
ll.Fail();
}
ll.Succeed();
}
private List<string> GetGameTypeEntries() private List<string> GetGameTypeEntries()
{ {
List<string> gameEntries = new List<string> { ALL_GAME_TYPE }; List<string> gameEntries = new List<string> {ALL_GAME_TYPE};
gameEntries.AddRange(GameRegistry.Games.Values.Select(gameType => gameType.HumanFriendlyGameName)); gameEntries.AddRange(GameRegistry.Games.Values.Select(gameType => gameType.HumanFriendlyGameName));
gameEntries.Sort(); gameEntries.Sort();
return gameEntries; return gameEntries;
@ -216,4 +137,4 @@ namespace Wabbajack
settings.OnlyInstalled = OnlyInstalled; settings.OnlyInstalled = OnlyInstalled;
} }
} }
} }

View File

@ -16,6 +16,7 @@ using Wabbajack.DTOs;
using Wabbajack.DTOs.ServerResponses; using Wabbajack.DTOs.ServerResponses;
using Wabbajack.Lib; using Wabbajack.Lib;
using Wabbajack.Lib.Extensions; using Wabbajack.Lib.Extensions;
using Wabbajack.Messages;
using Wabbajack.Networking.WabbajackClientApi; using Wabbajack.Networking.WabbajackClientApi;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
@ -126,7 +127,7 @@ namespace Wabbajack
ArchiveStatus = a.Status, ArchiveStatus = a.Status,
IsFailing = a.Status != ArchiveStatus.InValid IsFailing = a.Status != ArchiveStatus.InValid
})); }));
_parent.MWVM.NavigateTo(_parent.MWVM.ModListContentsVM.Value); NavigateToGlobal.Send(NavigateToGlobal.ScreenType.ModListContents);
} }
finally finally
{ {

View File

@ -15,6 +15,7 @@ using Microsoft.WindowsAPICodePack.Shell;
using Wabbajack.DTOs.JsonConverters; using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Lib.Extensions; using Wabbajack.Lib.Extensions;
using Wabbajack.Lib.Interventions; using Wabbajack.Lib.Interventions;
using Wabbajack.Messages;
using Wabbajack.RateLimiter; using Wabbajack.RateLimiter;
using Wabbajack.View_Models; using Wabbajack.View_Models;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
@ -98,7 +99,7 @@ public class InstallerVM : BackNavigatingVM, IBackNavigatingVM, ICpuStatusVM
public ReactiveCommand<Unit, Unit> GoToInstallCommand { get; } public ReactiveCommand<Unit, Unit> GoToInstallCommand { get; }
public ReactiveCommand<Unit, Unit> BeginCommand { get; } public ReactiveCommand<Unit, Unit> BeginCommand { get; }
public InstallerVM(ILogger<InstallerVM> logger, MainWindowVM mainWindowVM, IServiceProvider serviceProvider) : base(logger, mainWindowVM) public InstallerVM(ILogger<InstallerVM> logger, MainWindowVM mainWindowVM, IServiceProvider serviceProvider) : base(logger)
{ {
_logger = logger; _logger = logger;
@ -162,10 +163,7 @@ public class InstallerVM : BackNavigatingVM, IBackNavigatingVM, ICpuStatusVM
MWVM.Settings.Installer.LastInstalledListLocation = ModListLocation.TargetPath; MWVM.Settings.Installer.LastInstalledListLocation = ModListLocation.TargetPath;
}) })
.DisposeWith(CompositeDisposable); .DisposeWith(CompositeDisposable);
_IsActive = this.ConstructIsActive(MWVM)
.ToGuiProperty(this, nameof(IsActive));
// Active path represents the path to currently have loaded // Active path represents the path to currently have loaded
// If we're not actively showing, then "unload" the active path // If we're not actively showing, then "unload" the active path
var activePath = Observable.CombineLatest( var activePath = Observable.CombineLatest(
@ -239,7 +237,7 @@ public class InstallerVM : BackNavigatingVM, IBackNavigatingVM, ICpuStatusVM
{ {
StartedInstallation = false; StartedInstallation = false;
Completed = null; Completed = null;
mainWindowVM.NavigateTo(mainWindowVM.ModeSelectionVM); NavigateToGlobal.Send(NavigateToGlobal.ScreenType.ModeSelectionView);
}, },
canExecute: Observable.CombineLatest( canExecute: Observable.CombineLatest(
this.WhenAny(x => x.Installing) this.WhenAny(x => x.Installing)

View File

@ -3,6 +3,7 @@ using ReactiveUI;
using ReactiveUI.Fody.Helpers; using ReactiveUI.Fody.Helpers;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -14,6 +15,7 @@ using Wabbajack.Common;
using Wabbajack.Downloaders.GameFile; using Wabbajack.Downloaders.GameFile;
using Wabbajack.Lib; using Wabbajack.Lib;
using Wabbajack.Lib.Interventions; using Wabbajack.Lib.Interventions;
using Wabbajack.Messages;
using Wabbajack.Networking.WabbajackClientApi; using Wabbajack.Networking.WabbajackClientApi;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.View_Models; using Wabbajack.View_Models;
@ -38,7 +40,7 @@ namespace Wabbajack
public readonly Lazy<CompilerVM> Compiler; public readonly Lazy<CompilerVM> Compiler;
public readonly Lazy<InstallerVM> Installer; public readonly Lazy<InstallerVM> Installer;
public readonly Lazy<SettingsVM> SettingsPane; public readonly Lazy<SettingsVM> SettingsPane;
public readonly Lazy<ModListGalleryVM> Gallery; public readonly ModListGalleryVM Gallery;
public readonly ModeSelectionVM ModeSelectionVM; public readonly ModeSelectionVM ModeSelectionVM;
public readonly Lazy<ModListContentsVM> ModListContentsVM; public readonly Lazy<ModListContentsVM> ModListContentsVM;
public readonly UserInterventionHandlers UserInterventionHandlers; public readonly UserInterventionHandlers UserInterventionHandlers;
@ -55,7 +57,7 @@ namespace Wabbajack
public bool UpdateAvailable { get; private set; } public bool UpdateAvailable { get; private set; }
public MainWindowVM(ILogger<MainWindowVM> logger, MainSettings settings, Client wjClient, public MainWindowVM(ILogger<MainWindowVM> logger, MainSettings settings, Client wjClient,
IServiceProvider serviceProvider) IServiceProvider serviceProvider, ModeSelectionVM modeSelectionVM, ModListGalleryVM modListGalleryVM)
{ {
_logger = logger; _logger = logger;
_wjClient = wjClient; _wjClient = wjClient;
@ -64,12 +66,15 @@ namespace Wabbajack
Installer = new Lazy<InstallerVM>(() => new InstallerVM(serviceProvider.GetRequiredService<ILogger<InstallerVM>>(), this, serviceProvider)); Installer = new Lazy<InstallerVM>(() => new InstallerVM(serviceProvider.GetRequiredService<ILogger<InstallerVM>>(), this, serviceProvider));
Compiler = new Lazy<CompilerVM>(() => new CompilerVM(serviceProvider.GetRequiredService<ILogger<CompilerVM>>(), this)); Compiler = new Lazy<CompilerVM>(() => new CompilerVM(serviceProvider.GetRequiredService<ILogger<CompilerVM>>(), this));
SettingsPane = new Lazy<SettingsVM>(() => new SettingsVM(serviceProvider.GetRequiredService<ILogger<SettingsVM>>(), this, serviceProvider)); SettingsPane = new Lazy<SettingsVM>(() => new SettingsVM(serviceProvider.GetRequiredService<ILogger<SettingsVM>>(), this, serviceProvider));
Gallery = new Lazy<ModListGalleryVM>(() => new ModListGalleryVM(serviceProvider.GetRequiredService<ILogger<ModListGalleryVM>>(), this, Gallery = modListGalleryVM;
serviceProvider.GetRequiredService<Client>(), serviceProvider.GetRequiredService<GameLocator>(), serviceProvider)); ModeSelectionVM = modeSelectionVM;
ModeSelectionVM = new ModeSelectionVM(this);
ModListContentsVM = new Lazy<ModListContentsVM>(() => new ModListContentsVM(serviceProvider.GetRequiredService<ILogger<ModListContentsVM>>(), this)); ModListContentsVM = new Lazy<ModListContentsVM>(() => new ModListContentsVM(serviceProvider.GetRequiredService<ILogger<ModListContentsVM>>(), this));
UserInterventionHandlers = new UserInterventionHandlers(serviceProvider.GetRequiredService<ILogger<UserInterventionHandlers>>(), this); UserInterventionHandlers = new UserInterventionHandlers(serviceProvider.GetRequiredService<ILogger<UserInterventionHandlers>>(), this);
MessageBus.Current.Listen<NavigateToGlobal>()
.Subscribe(m => HandleNavigateTo(m.Screen))
.DisposeWith(CompositeDisposable);
// Set up logging // Set up logging
/* TODO /* TODO
Utils.LogMessages Utils.LogMessages
@ -116,12 +121,12 @@ namespace Wabbajack
if (IsStartingFromModlist(out var path)) if (IsStartingFromModlist(out var path))
{ {
Installer.Value.ModListLocation.TargetPath = path; Installer.Value.ModListLocation.TargetPath = path;
NavigateTo(Installer.Value); NavigateToGlobal.Send(NavigateToGlobal.ScreenType.Installer);
} }
else else
{ {
// Start on mode selection // Start on mode selection
NavigateTo(ModeSelectionVM); NavigateToGlobal.Send(NavigateToGlobal.ScreenType.ModeSelectionView);
} }
try try
@ -147,7 +152,19 @@ namespace Wabbajack
OpenSettingsCommand = ReactiveCommand.Create( OpenSettingsCommand = ReactiveCommand.Create(
canExecute: this.WhenAny(x => x.ActivePane) canExecute: this.WhenAny(x => x.ActivePane)
.Select(active => !SettingsPane.IsValueCreated || !object.ReferenceEquals(active, SettingsPane.Value)), .Select(active => !SettingsPane.IsValueCreated || !object.ReferenceEquals(active, SettingsPane.Value)),
execute: () => NavigateTo(SettingsPane.Value)); execute: () => NavigateToGlobal.Send(NavigateToGlobal.ScreenType.Settings));
}
private void HandleNavigateTo(NavigateToGlobal.ScreenType s)
{
ActivePane = s switch
{
NavigateToGlobal.ScreenType.ModeSelectionView => ModeSelectionVM,
NavigateToGlobal.ScreenType.ModListGallery => Gallery,
NavigateToGlobal.ScreenType.Installer => Installer.Value,
NavigateToGlobal.ScreenType.Settings => SettingsPane.Value,
_ => ActivePane
};
} }
private static bool IsStartingFromModlist(out AbsolutePath modlistPath) private static bool IsStartingFromModlist(out AbsolutePath modlistPath)
@ -171,21 +188,23 @@ namespace Wabbajack
if (path == default) return; if (path == default) return;
var installer = Installer.Value; var installer = Installer.Value;
Settings.Installer.LastInstalledListLocation = path; Settings.Installer.LastInstalledListLocation = path;
NavigateTo(installer); NavigateToGlobal.Send(NavigateToGlobal.ScreenType.Installer);
installer.ModListLocation.TargetPath = path; installer.ModListLocation.TargetPath = path;
} }
/*
public void NavigateTo(ViewModel vm) public void NavigateTo(ViewModel vm)
{ {
ActivePane = vm; ActivePane = vm;
} }*/
/*
public void NavigateTo<T>(T vm) public void NavigateTo<T>(T vm)
where T : ViewModel, IBackNavigatingVM where T : ViewModel, IBackNavigatingVM
{ {
vm.NavigateBackTarget = ActivePane; vm.NavigateBackTarget = ActivePane;
ActivePane = vm; ActivePane = vm;
} }*/
public async Task ShutdownApplication() public async Task ShutdownApplication()
{ {

View File

@ -31,7 +31,7 @@ namespace Wabbajack.View_Models
private static readonly Regex NameMatcher = new(@"(?<=\.)[^\.]+(?=\+State)", RegexOptions.Compiled); private static readonly Regex NameMatcher = new(@"(?<=\.)[^\.]+(?=\+State)", RegexOptions.Compiled);
private readonly ILogger<ModListContentsVM> _logger; private readonly ILogger<ModListContentsVM> _logger;
public ModListContentsVM(ILogger<ModListContentsVM> logger, MainWindowVM mwvm) : base(logger, mwvm) public ModListContentsVM(ILogger<ModListContentsVM> logger, MainWindowVM mwvm) : base(logger)
{ {
_logger = logger; _logger = logger;
_mwvm = mwvm; _mwvm = mwvm;

View File

@ -8,6 +8,7 @@ using System.Reactive.Linq;
using System.Windows.Input; using System.Windows.Input;
using Wabbajack.Common; using Wabbajack.Common;
using Wabbajack.Lib; using Wabbajack.Lib;
using Wabbajack.Messages;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
namespace Wabbajack namespace Wabbajack
@ -20,23 +21,23 @@ namespace Wabbajack
public ICommand CompileCommand { get; } public ICommand CompileCommand { get; }
public ReactiveCommand<Unit, Unit> UpdateCommand { get; } public ReactiveCommand<Unit, Unit> UpdateCommand { get; }
public ModeSelectionVM(MainWindowVM mainVM) public ModeSelectionVM()
{ {
_mainVM = mainVM;
InstallCommand = ReactiveCommand.Create( InstallCommand = ReactiveCommand.Create(
execute: () => execute: () =>
{ {
/* TODO
var path = mainVM.Settings.Installer.LastInstalledListLocation; var path = mainVM.Settings.Installer.LastInstalledListLocation;
if (path == default || !path.FileExists()) if (path == default || !path.FileExists())
{ {
path = UIUtils.OpenFileDialog($"*{Ext.Wabbajack}|*{Ext.Wabbajack}"); path = UIUtils.OpenFileDialog($"*{Ext.Wabbajack}|*{Ext.Wabbajack}");
} }
_mainVM.OpenInstaller(path); _mainVM.OpenInstaller(path);
*/
}); });
CompileCommand = ReactiveCommand.Create(() => mainVM.NavigateTo(mainVM.Compiler.Value)); CompileCommand = ReactiveCommand.Create(() => NavigateToGlobal.Send(NavigateToGlobal.ScreenType.Compiler));
BrowseCommand = ReactiveCommand.Create(() => mainVM.NavigateTo(mainVM.Gallery.Value)); BrowseCommand = ReactiveCommand.Create(() => NavigateToGlobal.Send(NavigateToGlobal.ScreenType.ModListGallery));
} }
} }
} }

View File

@ -34,7 +34,7 @@ namespace Wabbajack.View_Models.Settings
private readonly Client _wjClient; private readonly Client _wjClient;
private IObservable<bool> IsUploading { get; } private IObservable<bool> IsUploading { get; }
public AuthorFilesVM(ILogger<AuthorFilesVM> logger, WabbajackApiTokenProvider token, Client wjClient, SettingsVM vm) : base(logger, vm.MWVM) public AuthorFilesVM(ILogger<AuthorFilesVM> logger, WabbajackApiTokenProvider token, Client wjClient, SettingsVM vm) : base(logger)
{ {
_token = token; _token = token;
_wjClient = wjClient; _wjClient = wjClient;

View File

@ -20,7 +20,7 @@ namespace Wabbajack
public List<LoginTargetVM> Downloaders { get; } public List<LoginTargetVM> Downloaders { get; }
public LoginManagerVM(ILogger<LoginManagerVM> logger, SettingsVM settingsVM) public LoginManagerVM(ILogger<LoginManagerVM> logger, SettingsVM settingsVM)
: base(logger, settingsVM.MWVM) : base(logger)
{ {
/* /*
Downloaders = DownloadDispatcher.Downloaders Downloaders = DownloadDispatcher.Downloaders

View File

@ -28,7 +28,7 @@ namespace Wabbajack
public ICommand OpenTerminalCommand { get; } public ICommand OpenTerminalCommand { get; }
public SettingsVM(ILogger<SettingsVM> logger, MainWindowVM mainWindowVM, IServiceProvider provider) public SettingsVM(ILogger<SettingsVM> logger, MainWindowVM mainWindowVM, IServiceProvider provider)
: base(logger, mainWindowVM) : base(logger)
{ {
MWVM = mainWindowVM; MWVM = mainWindowVM;
Login = new LoginManagerVM(provider.GetRequiredService<ILogger<LoginManagerVM>>(), this); Login = new LoginManagerVM(provider.GetRequiredService<ILogger<LoginManagerVM>>(), this);

View File

@ -7,6 +7,7 @@ using ReactiveUI;
using Wabbajack.Common; using Wabbajack.Common;
using Wabbajack.Lib; using Wabbajack.Lib;
using Wabbajack.Lib.Interventions; using Wabbajack.Lib.Interventions;
using Wabbajack.Messages;
namespace Wabbajack namespace Wabbajack
{ {
@ -28,11 +29,11 @@ namespace Wabbajack
var cancel = new CancellationTokenSource(); var cancel = new CancellationTokenSource();
var oldPane = MainWindow.ActivePane; var oldPane = MainWindow.ActivePane;
using var vm = await WebBrowserVM.GetNew(_logger); using var vm = await WebBrowserVM.GetNew(_logger);
MainWindow.NavigateTo(vm); NavigateTo.Send(vm);
vm.BackCommand = ReactiveCommand.Create(() => vm.BackCommand = ReactiveCommand.Create(() =>
{ {
cancel.Cancel(); cancel.Cancel();
MainWindow.NavigateTo(oldPane); NavigateTo.Send(oldPane);
intervention.Cancel(); intervention.Cancel();
}); });
@ -54,7 +55,7 @@ namespace Wabbajack
wait.Dispose(); wait.Dispose();
} }
MainWindow.NavigateTo(oldPane); NavigateTo.Send(oldPane);
} }
public async Task Handle(IStatusMessage msg) public async Task Handle(IStatusMessage msg)

View File

@ -19,19 +19,19 @@ namespace Wabbajack
this.WhenAny(x => x.ViewModel.ModLists) this.WhenAny(x => x.ViewModel.ModLists)
.BindToStrict(this, x => x.ModListGalleryControl.ItemsSource) .BindToStrict(this, x => x.ModListGalleryControl.ItemsSource)
.DisposeWith(dispose); .DisposeWith(dispose);
Observable.CombineLatest(
this.WhenAny(x => x.ViewModel.Error), this.WhenAny(x => x.ViewModel.LoadingLock.IsLoading)
this.WhenAny(x => x.ViewModel.Loaded),
resultSelector: (err, loaded) =>
{
if (!err?.Succeeded ?? false) return true;
return !loaded;
})
.DistinctUntilChanged()
.Select(x => x ? Visibility.Visible : Visibility.Collapsed) .Select(x => x ? Visibility.Visible : Visibility.Collapsed)
.StartWith(Visibility.Collapsed) .StartWith(Visibility.Collapsed)
.BindToStrict(this, x => x.LoadingRing.Visibility) .BindTo(this, x => x.LoadingRing.Visibility)
.DisposeWith(dispose); .DisposeWith(dispose);
this.WhenAny(x => x.ViewModel.LoadingLock.ErrorState)
.Select(e => (e?.Succeeded ?? true) ? Visibility.Collapsed : Visibility.Visible)
.StartWith(Visibility.Collapsed)
.BindToStrict(this, x => x.ErrorIcon.Visibility)
.DisposeWith(dispose);
Observable.CombineLatest( Observable.CombineLatest(
this.WhenAny(x => x.ViewModel.ModLists.Count) this.WhenAny(x => x.ViewModel.ModLists.Count)
.Select(x => x > 0), .Select(x => x > 0),
@ -45,11 +45,7 @@ namespace Wabbajack
.StartWith(Visibility.Collapsed) .StartWith(Visibility.Collapsed)
.BindToStrict(this, x => x.NoneFound.Visibility) .BindToStrict(this, x => x.NoneFound.Visibility)
.DisposeWith(dispose); .DisposeWith(dispose);
this.WhenAny(x => x.ViewModel.Error)
.Select(e => (e?.Succeeded ?? true) ? Visibility.Collapsed : Visibility.Visible)
.StartWith(Visibility.Collapsed)
.BindToStrict(this, x => x.ErrorIcon.Visibility)
.DisposeWith(dispose);
this.BindStrict(ViewModel, vm => vm.Search, x => x.SearchBox.Text) this.BindStrict(ViewModel, vm => vm.Search, x => x.SearchBox.Text)
.DisposeWith(dispose); .DisposeWith(dispose);

View File

@ -60,7 +60,7 @@
<PackageReference Include="CefSharp.Wpf" Version="91.1.230"> <PackageReference Include="CefSharp.Wpf" Version="91.1.230">
<NoWarn>NU1701</NoWarn> <NoWarn>NU1701</NoWarn>
</PackageReference> </PackageReference>
<PackageReference Include="DynamicData" Version="7.2.1" /> <PackageReference Include="DynamicData" Version="7.3.1" />
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.1.0"> <PackageReference Include="Extended.Wpf.Toolkit" Version="4.1.0">
<NoWarn>NU1701</NoWarn> <NoWarn>NU1701</NoWarn>
</PackageReference> </PackageReference>
@ -78,9 +78,9 @@
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.4" /> <PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.4" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
<PackageReference Include="PInvoke.User32" Version="0.7.104" /> <PackageReference Include="PInvoke.User32" Version="0.7.104" />
<PackageReference Include="ReactiveUI" Version="14.1.1" /> <PackageReference Include="ReactiveUI" Version="16.2.6" />
<PackageReference Include="ReactiveUI.Fody" Version="14.1.1" /> <PackageReference Include="ReactiveUI.Fody" Version="16.2.6" />
<PackageReference Include="ReactiveUI.WPF" Version="14.1.1" /> <PackageReference Include="ReactiveUI.WPF" Version="16.2.6" />
<PackageReference Include="Silk.NET.DXGI" Version="2.6.0" /> <PackageReference Include="Silk.NET.DXGI" Version="2.6.0" />
<PackageReference Include="WPFThemes.DarkBlend" Version="1.0.8" /> <PackageReference Include="WPFThemes.DarkBlend" Version="1.0.8" />
</ItemGroup> </ItemGroup>
@ -104,4 +104,8 @@
<SplashScreen Include="Resources\Wabba_Mouth_Small.png" /> <SplashScreen Include="Resources\Wabba_Mouth_Small.png" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Models" />
</ItemGroup>
</Project> </Project>

View File

@ -1,5 +1,4 @@
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Reactive; using System.Reactive;

View File

@ -4,8 +4,8 @@ namespace Wabbajack
{ {
public struct ErrorResponse : IErrorResponse public struct ErrorResponse : IErrorResponse
{ {
public readonly static ErrorResponse Success = Succeed(); public static readonly ErrorResponse Success = Succeed();
public readonly static ErrorResponse Failure = new ErrorResponse(); public static readonly ErrorResponse Failure = new ErrorResponse();
public bool Succeeded { get; } public bool Succeeded { get; }
public Exception? Exception { get; } public Exception? Exception { get; }

View File

@ -0,0 +1,74 @@
using System;
using System.Reactive.Disposables;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace Wabbajack.Models;
public class LoadingLock : ReactiveObject, IDisposable
{
private readonly CompositeDisposable _disposable;
public ErrorResponse? ErrorState { get; set; }
public LoadingLock()
{
_disposable = new CompositeDisposable();
this.WhenAnyValue(vm => vm.LoadLevel)
.Subscribe(v => IsLoading = v > 0)
.DisposeWith(_disposable);
}
[Reactive] public int LoadLevel { get; private set; }
[Reactive] public bool IsLoading { get; private set; }
public void Dispose()
{
GC.SuppressFinalize(this);
_disposable.Dispose();
}
public LockContext WithLoading()
{
RxApp.MainThreadScheduler.Schedule(0, (_, _) => { LoadLevel++;
return Disposable.Empty;
});
return new LockContext(this);
}
public class LockContext : IDisposable
{
private readonly LoadingLock _parent;
private bool _disposed;
public LockContext(LoadingLock parent)
{
_parent = parent;
_disposed = false;
}
public void Succeed()
{
_parent.ErrorState = ErrorResponse.Success;
Dispose();
}
public void Fail()
{
_parent.ErrorState = ErrorResponse.Failure;
Dispose();
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
RxApp.MainThreadScheduler.Schedule(0, (_, _) => { _parent.LoadLevel--;
return Disposable.Empty;
});
}
}
}

View File

@ -4,15 +4,18 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Wabbajack.Models;
namespace Wabbajack.Lib namespace Wabbajack.Lib
{ {
public class ViewModel : ReactiveObject, IDisposable public class ViewModel : ReactiveObject, IDisposable, IActivatableViewModel
{ {
private readonly Lazy<CompositeDisposable> _compositeDisposable = new Lazy<CompositeDisposable>(); private readonly Lazy<CompositeDisposable> _compositeDisposable = new();
[JsonIgnore] [JsonIgnore]
public CompositeDisposable CompositeDisposable => _compositeDisposable.Value; public CompositeDisposable CompositeDisposable => _compositeDisposable.Value;
[JsonIgnore] public LoadingLock LoadingLock { get; } = new();
public virtual void Dispose() public virtual void Dispose()
{ {
if (_compositeDisposable.IsValueCreated) if (_compositeDisposable.IsValueCreated)
@ -30,5 +33,7 @@ namespace Wabbajack.Lib
item = newItem; item = newItem;
this.RaisePropertyChanged(propertyName); this.RaisePropertyChanged(propertyName);
} }
public ViewModelActivator Activator { get; } = new();
} }
} }

View File

@ -35,10 +35,10 @@
<Version>0.50.0</Version> <Version>0.50.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="ReactiveUI"> <PackageReference Include="ReactiveUI">
<Version>14.1.1</Version> <Version>16.2.6</Version>
</PackageReference> </PackageReference>
<PackageReference Include="ReactiveUI.Fody"> <PackageReference Include="ReactiveUI.Fody">
<Version>14.1.1</Version> <Version>16.2.6</Version>
</PackageReference> </PackageReference>
<PackageReference Include="SharpCompress"> <PackageReference Include="SharpCompress">
<Version>0.28.3</Version> <Version>0.28.3</Version>