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

View File

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

View File

@ -1,9 +1,13 @@
using System;

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using DynamicData;
using DynamicData.Binding;
@ -17,6 +21,7 @@ using Wabbajack.DTOs;
using Wabbajack.Hashing.xxHash64;
using Wabbajack.Lib.Extensions;
using Wabbajack.Networking.WabbajackClientApi;
using Wabbajack.Services.OSIntegrated;
using Wabbajack.Services.OSIntegrated.Services;
namespace Wabbajack
@ -24,68 +29,48 @@ namespace Wabbajack
public class ModListGalleryVM : BackNavigatingVM
{
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";
[Reactive]
public IErrorResponse Error { get; set; }
[Reactive] public IErrorResponse Error { get; set; }
[Reactive]
public string Search { get; set; }
[Reactive] public string Search { get; set; }
[Reactive]
public bool OnlyInstalled { get; set; }
[Reactive] public bool OnlyInstalled { get; set; }
[Reactive]
public bool ShowNSFW { get; set; }
[Reactive] public bool ShowNSFW { get; set; }
[Reactive]
public bool ShowUtilityLists { get; set; }
[Reactive] public bool ShowUtilityLists { get; set; }
[Reactive]
public string GameType { get; set; }
[Reactive] 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 ILogger<ModListGalleryVM> _logger;
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 ICommand ClearFiltersCommand { get; }
public ICommand ClearFiltersCommand { get; set; }
public ModListGalleryVM(ILogger<ModListGalleryVM> logger, MainWindowVM mainWindowVM, Client wjClient,
GameLocator locator, IServiceProvider provider)
: base(logger, mainWindowVM)
public ModListGalleryVM(ILogger<ModListGalleryVM> logger, Client wjClient,
GameLocator locator, SettingsManager settingsManager, ModListDownloadMaintainer maintainer)
: base(logger)
{
MWVM = mainWindowVM;
_wjClient = wjClient;
_logger = logger;
_locator = locator;
// 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);
_maintainer = maintainer;
ClearFiltersCommand = ReactiveCommand.Create(
() =>
{
@ -97,101 +82,16 @@ namespace Wabbajack
});
this.WhenAny(x => x.OnlyInstalled)
.Subscribe(val =>
{
if(val)
GameType = ALL_GAME_TYPE;
})
.DisposeWith(CompositeDisposable);
var sourceList = Observable.Return(Unit.Default)
.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);
this.WhenActivated(disposables =>
{
var _ = LoadModLists();
_modLists.Connect()
.ObserveOn(RxApp.MainThreadScheduler)
.Bind(out _filteredModLists)
.Subscribe()
.DisposeWith(disposables);
});
}
public override void Unload()
@ -199,9 +99,30 @@ namespace Wabbajack
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()
{
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.Sort();
return gameEntries;
@ -216,4 +137,4 @@ namespace Wabbajack
settings.OnlyInstalled = OnlyInstalled;
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -31,7 +31,7 @@ namespace Wabbajack.View_Models
private static readonly Regex NameMatcher = new(@"(?<=\.)[^\.]+(?=\+State)", RegexOptions.Compiled);
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;
_mwvm = mwvm;

View File

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

View File

@ -34,7 +34,7 @@ namespace Wabbajack.View_Models.Settings
private readonly Client _wjClient;
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;
_wjClient = wjClient;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,8 +4,8 @@ namespace Wabbajack
{
public struct ErrorResponse : IErrorResponse
{
public readonly static ErrorResponse Success = Succeed();
public readonly static ErrorResponse Failure = new ErrorResponse();
public static readonly ErrorResponse Success = Succeed();
public static readonly ErrorResponse Failure = new ErrorResponse();
public bool Succeeded { 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.Reactive.Disposables;
using System.Runtime.CompilerServices;
using Wabbajack.Models;
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]
public CompositeDisposable CompositeDisposable => _compositeDisposable.Value;
[JsonIgnore] public LoadingLock LoadingLock { get; } = new();
public virtual void Dispose()
{
if (_compositeDisposable.IsValueCreated)
@ -30,5 +33,7 @@ namespace Wabbajack.Lib
item = newItem;
this.RaisePropertyChanged(propertyName);
}
public ViewModelActivator Activator { get; } = new();
}
}

View File

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