Merge pull request #1676 from wabbajack-tools/resources-in-settings-page

Resources in settings page
This commit is contained in:
Timothy Baldridge 2021-10-22 05:31:18 -06:00 committed by GitHub
commit 56cd1d5f14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 330 additions and 13 deletions

View File

@ -0,0 +1,15 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Wabbajack.App.Controls.InstalledListView">
<Grid RowDefinitions="Auto, Auto" ColumnDefinitions="*, Auto">
<TextBlock Grid.Row="0" Grid.Column="0" x:Name="Title"></TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0" x:Name="InstallationPath"></TextBlock>
<Button Grid.Row="0" Grid.Column="1" Grid.RowSpan="2" x:Name="PlayButton">
<i:MaterialIcon Kind="PlayArrow"></i:MaterialIcon>
</Button>
</Grid>
</UserControl>

View File

@ -0,0 +1,28 @@
using Avalonia.Controls.Mixins;
using Avalonia.ReactiveUI;
using ReactiveUI;
using Wabbajack.App.Utilities;
namespace Wabbajack.App.Controls;
public partial class InstalledListView : ReactiveUserControl<InstalledListViewModel>, IActivatableView
{
public InstalledListView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel, vm => vm.Name, view => view.Title.Text)
.DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.InstallPath, view => view.Title.Text,
p => p.ToString())
.DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.Play, view => view.PlayButton)
.DisposeWith(disposables);
});
}
}

View File

@ -0,0 +1,32 @@
using System.Reactive;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack.App.Messages;
using Wabbajack.App.Screens;
using Wabbajack.App.ViewModels;
using Wabbajack.DTOs.SavedSettings;
using Wabbajack.Paths;
namespace Wabbajack.App.Controls;
public class InstalledListViewModel : ViewModelBase
{
private readonly InstallationConfigurationSetting _setting;
public AbsolutePath InstallPath => _setting.Install;
public string Name => _setting.Metadata?.Title ?? "";
public ReactiveCommand<Unit, Unit> Play { get; }
public InstalledListViewModel(InstallationConfigurationSetting setting)
{
Activator = new ViewModelActivator();
_setting = setting;
Play = ReactiveCommand.Create(() =>
{
MessageBus.Instance.Send(new ConfigureLauncher(InstallPath));
MessageBus.Instance.Send(new NavigateTo(typeof(LauncherViewModel)));
});
}
}

View File

@ -0,0 +1,16 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Wabbajack.App.Controls.ResourceView">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="ResourceName" Width="100" HorizontalAlignment="Left" VerticalAlignment="Center"></TextBlock>
<Label Width="100" HorizontalContentAlignment="Right" VerticalAlignment="Center">Tasks:</Label>
<TextBox x:Name="MaxTasks" Width="20" HorizontalAlignment="Left" VerticalAlignment="Center"></TextBox>
<Label Width="100" HorizontalContentAlignment="Right" VerticalAlignment="Center">Throughput:</Label>
<TextBox x:Name="MaxThroughput" Width="20" HorizontalAlignment="Left" VerticalAlignment="Center"></TextBox>
<Label Width="100" HorizontalContentAlignment="Right" VerticalAlignment="Center">Status:</Label>
<TextBlock x:Name="CurrentThrougput" Width="50" HorizontalAlignment="Left" VerticalAlignment="Center"></TextBlock>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,32 @@
using System.Reactive.Disposables;
using Avalonia.ReactiveUI;
using FluentFTP.Helpers;
using ReactiveUI;
namespace Wabbajack.App.Controls;
public partial class ResourceView : ReactiveUserControl<ResourceViewModel>, IActivatableView
{
public ResourceView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel, vm => vm.Name, view => view.ResourceName.Text)
.DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.MaxTasks, view => view.MaxTasks.Text)
.DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.MaxThroughput, view => view.MaxThroughput.Text)
.DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.CurrentThroughput, view => view.CurrentThrougput.Text,
val => val.FileSizeToString())
.DisposeWith(disposables);
});
}
}

View File

@ -0,0 +1,76 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Timers;using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack.App.ViewModels;
using Wabbajack.RateLimiter;
namespace Wabbajack.App.Controls;
public class ResourceViewModel : ViewModelBase, IActivatableViewModel, IDisposable
{
private readonly IResource _resource;
private readonly Timer _timer;
[Reactive]
public int MaxTasks { get; set; }
[Reactive]
public long MaxThroughput { get; set; }
[Reactive]
public long CurrentThroughput { get; set; }
[Reactive]
public string Name { get; set; }
public ResourceViewModel(IResource resource)
{
Activator = new ViewModelActivator();
_resource = resource;
_timer = new Timer(1.0);
Name = resource.Name;
this.WhenActivated(disposables =>
{
_timer.Elapsed += TimerElapsed;
_timer.Start();
Disposable.Create(() =>
{
_timer.Stop();
_timer.Elapsed -= TimerElapsed;
}).DisposeWith(disposables);
this.WhenAnyValue(vm => vm.MaxThroughput)
.Skip(1)
.Subscribe(v =>
{
_resource.MaxThroughput = MaxThroughput;
}).DisposeWith(disposables);
this.WhenAnyValue(vm => vm.MaxTasks)
.Skip(1)
.Subscribe(v =>
{
_resource.MaxTasks = MaxTasks;
}).DisposeWith(disposables);
});
}
private void TimerElapsed(object? sender, ElapsedEventArgs e)
{
MaxTasks = _resource.MaxTasks;
MaxThroughput = _resource.MaxThroughput;
CurrentThroughput = _resource.StatusReport.Transferred;
}
public void Dispose()
{
_timer.Dispose();
}
}

View File

@ -0,0 +1,22 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Wabbajack.App.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Wabbajack.App.Screens.PlaySelectView">
<Grid RowDefinitions="*">
<ItemsControl x:Name="Lists">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:InstalledListView></controls:InstalledListView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>

View File

@ -0,0 +1,19 @@
using Avalonia.Controls.Mixins;
using ReactiveUI;
using Wabbajack.App.Views;
namespace Wabbajack.App.Screens;
public partial class PlaySelectView : ScreenBase<PlaySelectViewModel>
{
public PlaySelectView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel, vm => vm.Items, view => view.Lists.Items)
.DisposeWith(disposables);
});
}
}

View File

@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack.App.Controls;
using Wabbajack.App.Models;
using Wabbajack.App.ViewModels;
using Wabbajack.Common;
using Wabbajack.DTOs.SavedSettings;
namespace Wabbajack.App.Screens;
public class PlaySelectViewModel : ViewModelBase, IActivatableViewModel
{
private readonly InstallationStateManager _manager;
[Reactive]
public IEnumerable<InstalledListViewModel> Items { get; set; }
public PlaySelectViewModel(InstallationStateManager manager)
{
_manager = manager;
Activator = new ViewModelActivator();
this.WhenActivated(disposables =>
{
LoadAndSetItems().FireAndForget();
Disposable.Empty.DisposeWith(disposables);
});
}
public async Task LoadAndSetItems()
{
var items = await _manager.GetAll();
Items = items.Settings.Select(a => new InstalledListViewModel(a)).ToArray();
}
}

View File

@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Wabbajack.App.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Wabbajack.App.Screens.SettingsView">
<WrapPanel Margin="40">
@ -26,5 +27,25 @@
</Grid>
</Border>
<Border x:Name="ResourcesBorder" Margin="5" BorderThickness="1">
<Grid RowDefinitions="Auto, Auto">
<TextBlock FontSize="20" Grid.ColumnSpan="4">Resource Limits</TextBlock>
<ItemsControl Grid.Row="1" x:Name="ResourceList">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:ResourceView></controls:ResourceView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Border>
</WrapPanel>
</UserControl>

View File

@ -16,6 +16,8 @@ namespace Wabbajack.App.Screens
.DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.NexusLogout, view => view.NexusLogOut)
.DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.Resources, view => view.ResourceList.Items)
.DisposeWith(disposables);
});
}

View File

@ -1,14 +1,18 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Microsoft.Extensions.Logging;
using ReactiveUI;
using Wabbajack.App.Controls;
using Wabbajack.App.Messages;
using Wabbajack.App.ViewModels;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter;
using Wabbajack.Services.OSIntegrated.TokenProviders;
namespace Wabbajack.App.Screens
@ -23,10 +27,12 @@ namespace Wabbajack.App.Screens
public FileSystemWatcher Watcher { get; set; }
private readonly Subject<AbsolutePath> _fileSystemEvents = new();
public SettingsViewModel(ILogger<SettingsViewModel> logger, Configuration configuration, NexusApiTokenProvider nexusProvider)
public readonly IEnumerable<ResourceViewModel> Resources;
public SettingsViewModel(ILogger<SettingsViewModel> logger, Configuration configuration, NexusApiTokenProvider nexusProvider, IEnumerable<IResource> resources)
{
_logger = logger;
Resources = resources.Select(r => new ResourceViewModel(r)).ToArray();
Activator = new ViewModelActivator();
this.WhenActivated(disposables =>

View File

@ -36,11 +36,11 @@ namespace Wabbajack.App
services.AddSingleton<MessageBus>();
services.AddSingleton<MainWindow>();
services.AddSingleton<BrowseViewModel>();
services.AddTransient<BrowseItemViewModel>();
services.AddTransient<LogViewModel>();
services.AddTransient<InstalledListViewModel>();
services.AddDTOConverters();
services.AddDTOSerializer();
services.AddSingleton<ModeSelectionViewModel>();
@ -55,11 +55,13 @@ namespace Wabbajack.App
services.AddSingleton<IScreenView, SettingsView>();
services.AddSingleton<IScreenView, BrowseView>();
services.AddSingleton<IScreenView, LauncherView>();
services.AddSingleton<IScreenView, PlaySelectView>();
services.AddSingleton<InstallationStateManager>();
services.AddSingleton<HttpClient>();
services.AddSingleton<LogScreenViewModel>();
services.AddSingleton<PlaySelectViewModel>();
services.AddAllSingleton<IReceiverMarker, ErrorPageViewModel>();
services.AddAllSingleton<IReceiverMarker, StandardInstallationViewModel>();
services.AddAllSingleton<IReceiverMarker, InstallConfigurationViewModel>();

View File

@ -18,7 +18,7 @@
<controls:LargeIconButton Grid.Column="0" Text="Browse" Icon="CloudDownload" x:Name="BrowseButton"></controls:LargeIconButton>
<controls:LargeIconButton Grid.Column="1" Text="Install" Icon="HarddiskPlus" x:Name="InstallButton"></controls:LargeIconButton>
<controls:LargeIconButton Grid.Column="2" Text="Compile" Icon="DatabaseImport" x:Name="CompileButton"></controls:LargeIconButton>
<controls:LargeIconButton Grid.Column="3" Text="Play" Icon="TelevisionPlay"></controls:LargeIconButton>
<controls:LargeIconButton Grid.Column="3" Text="Play" Icon="TelevisionPlay" x:Name="LaunchButton"></controls:LargeIconButton>
</Grid>
</Grid>
</UserControl>

View File

@ -29,6 +29,11 @@ namespace Wabbajack.App.Views
{
MessageBus.Instance.Send(new NavigateTo(typeof(CompilerConfigurationViewModel)));
}).DisposeWith(disposables);
LaunchButton.Button.Command = ReactiveCommand.Create(() =>
{
MessageBus.Instance.Send(new NavigateTo(typeof(PlaySelectViewModel)));
});
});
}

View File

@ -7,7 +7,8 @@ namespace Wabbajack.RateLimiter
{
StatusReport StatusReport { get; }
string Name { get; }
int MaxTasks { get; set; }
long MaxThroughput { get; set; }
}
public interface IResource<T> : IResource
{

View File

@ -16,8 +16,8 @@ namespace Wabbajack.RateLimiter
private readonly ConcurrentDictionary<ulong, Job<T>> _tasks;
private ulong _nextId = 0;
private long _totalUsed = 0;
private readonly int _maxTasks;
private readonly long _maxThroughput;
public int MaxTasks { get; set; }
public long MaxThroughput { get; set; }
private readonly string _humanName;
public string Name => _humanName;
@ -26,10 +26,10 @@ namespace Wabbajack.RateLimiter
public Resource(string? humanName = null, int? maxTasks = 0, long maxThroughput = long.MaxValue)
{
_humanName = humanName ?? "<unknown>";
_maxTasks = maxTasks ?? Environment.ProcessorCount;
_maxThroughput = maxThroughput;
MaxTasks = maxTasks ?? Environment.ProcessorCount;
MaxThroughput = maxThroughput;
_semaphore = new SemaphoreSlim(_maxTasks);
_semaphore = new SemaphoreSlim(MaxTasks);
_channel = Channel.CreateBounded<PendingReport>(10);
_tasks = new ();
@ -44,14 +44,14 @@ namespace Wabbajack.RateLimiter
await foreach (var item in _channel.Reader.ReadAllAsync(token))
{
Interlocked.Add(ref _totalUsed, item.Size);
if (_maxThroughput == long.MaxValue)
if (MaxThroughput == long.MaxValue)
{
item.Result.TrySetResult();
sw.Restart();
continue;
}
var span = TimeSpan.FromSeconds((double)item.Size / _maxThroughput);
var span = TimeSpan.FromSeconds((double)item.Size / MaxThroughput);
await Task.Delay(span, token);