Install/Compile views display ConfirmationInterventions in CPU area

This commit is contained in:
Justin Swanson 2019-12-08 18:19:36 -06:00
parent 886fbd13ad
commit 7f695a4a9e
20 changed files with 328 additions and 80 deletions

View File

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wabbajack.Common.StatusFeed
{
/// <summary>
/// Defines a message that requires user interaction. The user must perform some action
/// or make a choice.
/// </summary>
public interface IUserIntervention : IStatusMessage
{
/// <summary>
/// The user didn't make a choice, so this action should be aborted
/// </summary>
void Cancel();
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using ReactiveUI;
namespace Wabbajack.Common
{
public abstract class AUserIntervention : ReactiveObject, IUserIntervention
{
public DateTime Timestamp { get; } = DateTime.Now;
public abstract string ShortDescription { get; }
public abstract string ExtendedDescription { get; }
private bool _handled;
public bool Handled { get => _handled; set => this.RaiseAndSetIfChanged(ref _handled, value); }
public int CpuID { get; } = WorkQueue.CpuId;
public abstract void Cancel();
public ICommand CancelCommand { get; }
public AUserIntervention()
{
CancelCommand = ReactiveCommand.Create(() => Cancel());
}
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using ReactiveUI;
namespace Wabbajack.Common
{
public abstract class ConfirmationIntervention : AUserIntervention
{
public enum Choice
{
Continue,
Abort
}
private TaskCompletionSource<Choice> _source = new TaskCompletionSource<Choice>();
public Task<Choice> Task => _source.Task;
public ICommand ConfirmCommand { get; }
public ConfirmationIntervention()
{
ConfirmCommand = ReactiveCommand.Create(() => Confirm());
}
public override void Cancel()
{
Handled = true;
_source.SetResult(Choice.Abort);
}
public void Confirm()
{
Handled = true;
_source.SetResult(Choice.Continue);
}
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ReactiveUI;
using Wabbajack.Common.StatusFeed;
namespace Wabbajack.Common
{
/// <summary>
/// Defines a message that requires user interaction. The user must perform some action
/// or make a choice.
/// </summary>
public interface IUserIntervention : IStatusMessage, IReactiveObject
{
/// <summary>
/// The user didn't make a choice, so this action should be aborted
/// </summary>
void Cancel();
/// <summary>
/// Whether the interaction has been handled and no longer needs attention
/// Note: This needs to be Reactive so that users can monitor its status
/// </summary>
bool Handled { get; }
/// <summary>
/// WorkQueue job ID that is blocking on this intervention
/// </summary>
int CpuID { get; }
}
}

View File

@ -114,6 +114,7 @@
<Compile Include="SplittingStream.cs" />
<Compile Include="StatusFeed\AErrorMessage.cs" />
<Compile Include="StatusFeed\AStatusMessage.cs" />
<Compile Include="StatusFeed\Interventions\AUserIntervention.cs" />
<Compile Include="StatusFeed\Errors\7zipReturnError.cs" />
<Compile Include="StatusFeed\Errors\FileExtractionError.cs" />
<Compile Include="StatusFeed\Errors\GenericException.cs" />
@ -122,8 +123,9 @@
<Compile Include="StatusFeed\IError.cs" />
<Compile Include="StatusFeed\IException.cs" />
<Compile Include="StatusFeed\IInfo.cs" />
<Compile Include="StatusFeed\Interventions\ConfirmationIntervention.cs" />
<Compile Include="StatusFeed\IStatusMessage.cs" />
<Compile Include="StatusFeed\IUserIntervention.cs" />
<Compile Include="StatusFeed\Interventions\IUserIntervention.cs" />
<Compile Include="StatusFileStream.cs" />
<Compile Include="StatusUpdate.cs" />
<Compile Include="SteamHandler.cs" />

View File

@ -14,7 +14,10 @@ namespace Wabbajack.Common
internal BlockingCollection<Action>
Queue = new BlockingCollection<Action>(new ConcurrentStack<Action>());
[ThreadStatic] private static int CpuId;
public const int UnassignedCpuId = -1;
[ThreadStatic] private static int _cpuId = UnassignedCpuId;
public static int CpuId => _cpuId;
internal static bool WorkerThread => CurrentQueue != null;
[ThreadStatic] internal static WorkQueue CurrentQueue;
@ -24,6 +27,11 @@ namespace Wabbajack.Common
public static List<Thread> Threads { get; private set; }
// This is currently a lie, as it wires to the Utils singleton stream This is still good to have,
// so that logic related to a single WorkQueue can subscribe to this dummy member so that If/when we
// implement log messages in a non-singleton fashion, they will already be wired up properly.
public IObservable<IStatusMessage> LogMessages => Utils.LogMessages;
public WorkQueue(int threadCount = 0)
{
StartThreads(threadCount == 0 ? Environment.ProcessorCount : threadCount);
@ -48,7 +56,7 @@ namespace Wabbajack.Common
private void ThreadBody(int idx)
{
CpuId = idx;
_cpuId = idx;
CurrentQueue = this;
while (true)
@ -67,7 +75,7 @@ namespace Wabbajack.Common
Progress = progress,
ProgressPercent = progress / 100f,
Msg = msg,
ID = CpuId,
ID = _cpuId,
IsWorking = isWorking
});
}

View File

@ -1,9 +1,11 @@
using System;
using System.IO;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
using Wabbajack.Common;
using Wabbajack.Common.StatusFeed;
using Wabbajack.VirtualFileSystem;
namespace Wabbajack.Lib
@ -15,6 +17,7 @@ namespace Wabbajack.Lib
public void Dispose()
{
Queue?.Shutdown();
_subs.Dispose();
}
public Context VFS { get; private set; }
@ -38,6 +41,9 @@ namespace Wabbajack.Lib
private Subject<CPUStatus> _queueStatus { get; } = new Subject<CPUStatus>();
public IObservable<CPUStatus> QueueStatus => _queueStatus;
private Subject<IStatusMessage> _logMessages { get; } = new Subject<IStatusMessage>();
public IObservable<IStatusMessage> LogMessages => _logMessages;
private Subject<bool> _isRunning { get; } = new Subject<bool>();
public IObservable<bool> IsRunning => _isRunning;
@ -46,6 +52,8 @@ namespace Wabbajack.Lib
private int _configured;
private int _started;
private readonly CompositeDisposable _subs = new CompositeDisposable();
protected void ConfigureProcessor(int steps, int threads = 0)
{
if (1 == Interlocked.CompareExchange(ref _configured, 1, 1))
@ -54,7 +62,10 @@ namespace Wabbajack.Lib
}
Queue = new WorkQueue(threads);
UpdateTracker = new StatusUpdateTracker(steps);
Queue.Status.Subscribe(_queueStatus);
Queue.Status.Subscribe(_queueStatus)
.DisposeWith(_subs);
Queue.LogMessages.Subscribe(_logMessages)
.DisposeWith(_subs);
UpdateTracker.Progress.Subscribe(_percentCompleted);
UpdateTracker.StepName.Subscribe(_textStatus);
VFS = new Context(Queue) { UpdateTracker = UpdateTracker };

View File

@ -9,11 +9,8 @@ using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Wabbajack.Common;
using Wabbajack.Common.StatusFeed;
using Wabbajack.Common.StatusFeed.Errors;
using Wabbajack.Lib.LibCefHelpers;
using Wabbajack.Lib.Validation;
using Wabbajack.Lib.WebAutomation;
using Xilium.CefGlue.Common;
using File = Alphaleonis.Win32.Filesystem.File;
@ -183,7 +180,7 @@ namespace Wabbajack.Lib.Downloaders
}
}
public class RequestLoversLabLogin : AStatusMessage, IUserIntervention
public class RequestLoversLabLogin : AUserIntervention
{
public override string ShortDescription => "Getting LoversLab information";
public override string ExtendedDescription { get; }
@ -193,10 +190,13 @@ namespace Wabbajack.Lib.Downloaders
public void Resume(Helpers.Cookie[] cookies)
{
Handled = true;
_source.SetResult(cookies);
}
public void Cancel()
public override void Cancel()
{
Handled = true;
_source.SetCanceled();
}
}

View File

@ -10,7 +10,6 @@ using Wabbajack.Common;
using Wabbajack.Lib.CompilationSteps.CompilationErrors;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.NexusApi;
using Wabbajack.Lib.StatusMessages;
using Wabbajack.Lib.Validation;
using Directory = Alphaleonis.Win32.Filesystem.Directory;
using File = Alphaleonis.Win32.Filesystem.File;

View File

@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common.StatusFeed;
using Wabbajack.Common;
namespace Wabbajack.Lib.NexusApi
{
public class RequestNexusAuthorization : AStatusMessage, IUserIntervention
public class RequestNexusAuthorization : AUserIntervention
{
public override string ShortDescription => "Getting User's Nexus API Key";
public override string ExtendedDescription { get; }
@ -17,10 +17,13 @@ namespace Wabbajack.Lib.NexusApi
public void Resume(string apikey)
{
Handled = true;
_source.SetResult(apikey);
}
public void Cancel()
public override void Cancel()
{
Handled = true;
_source.SetCanceled();
}
}

View File

@ -3,24 +3,16 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common.StatusFeed;
using Wabbajack.Common;
namespace Wabbajack.Lib.StatusMessages
namespace Wabbajack.Lib
{
public class ConfirmUpdateOfExistingInstall : AStatusMessage, IUserIntervention
public class ConfirmUpdateOfExistingInstall : ConfirmationIntervention
{
public enum Choice
{
Continue,
Abort
}
public string OutputFolder { get; set; }
public string ModListName { get; set; }
public override string ShortDescription { get; } = "Do you want to overwrite existing files?";
private TaskCompletionSource<Choice> _source = new TaskCompletionSource<Choice>();
public Task<Choice> Task => _source.Task;
public override string ShortDescription { get; } = "Do you want to overwrite existing files?";
public override string ExtendedDescription
{
@ -29,15 +21,5 @@ namespace Wabbajack.Lib.StatusMessages
Any files that exist in {OutputFolder} will be changed to match the files found in the {ModListName} modlist. This means that save games will be removed, custom settings
will be reverted. Are you sure you wish to continue?";
}
public void Cancel()
{
_source.SetResult(Choice.Abort);
}
public void Confirm()
{
_source.SetResult(Choice.Continue);
}
}
}

View File

@ -43,6 +43,7 @@
<Color x:Key="Secondary">#03DAC6</Color>
<Color x:Key="DarkSecondary">#0e8f83</Color>
<Color x:Key="DarkerSecondary">#095952</Color>
<Color x:Key="SecondaryBackground">#042421</Color>
<Color x:Key="OffWhiteSeconday">#cef0ed</Color>
<Color x:Key="LightSecondary">#8cede5</Color>
<Color x:Key="IntenseSecondary">#00ffe7</Color>
@ -115,6 +116,7 @@
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}" />
<SolidColorBrush x:Key="DarkSecondaryBrush" Color="{StaticResource DarkSecondary}" />
<SolidColorBrush x:Key="DarkerSecondaryBrush" Color="{StaticResource DarkerSecondary}" />
<SolidColorBrush x:Key="SecondaryBackgroundBrush" Color="{StaticResource SecondaryBackground}" />
<SolidColorBrush x:Key="OffWhiteSecondayBrush" Color="{StaticResource OffWhiteSeconday}" />
<SolidColorBrush x:Key="LightSecondaryBrush" Color="{StaticResource LightSecondary}" />
<SolidColorBrush x:Key="IntenseSecondaryBrush" Color="{StaticResource IntenseSecondary}" />

View File

@ -19,7 +19,6 @@ using DynamicData;
using DynamicData.Binding;
using Wabbajack.Common.StatusFeed;
using System.Reactive;
using Wabbajack.Common.StatusFeed;
namespace Wabbajack
{
@ -79,6 +78,9 @@ namespace Wabbajack
private readonly ObservableAsPropertyHelper<ModManager?> _TargetManager;
public ModManager? TargetManager => _TargetManager.Value;
private readonly ObservableAsPropertyHelper<IUserIntervention> _ActiveGlobalUserIntervention;
public IUserIntervention ActiveGlobalUserIntervention => _ActiveGlobalUserIntervention.Value;
// Command properties
public IReactiveCommand ShowReportCommand { get; }
public IReactiveCommand OpenReadmeCommand { get; }
@ -293,6 +295,22 @@ namespace Wabbajack
InstallingMode = true;
})
.DisposeWith(CompositeDisposable);
// Listen for user interventions, and compile a dynamic list of all unhandled ones
var activeInterventions = this.WhenAny(x => x.Installer.ActiveInstallation)
.SelectMany(c => c?.LogMessages ?? Observable.Empty<IStatusMessage>())
.WhereCastable<IStatusMessage, IUserIntervention>()
.ToObservableChangeSet()
.AutoRefresh(i => i.Handled)
.Filter(i => !i.Handled)
.AsObservableList();
// Find the top intervention /w no CPU ID to be marked as "global"
_ActiveGlobalUserIntervention = activeInterventions.Connect()
.Filter(x => x.CpuID == WorkQueue.UnassignedCpuId)
.QueryWhenChanged(query => query.FirstOrDefault())
.ObserveOnGuiThread()
.ToProperty(this, nameof(ActiveGlobalUserIntervention));
}
private void ShowReport()

View File

@ -12,9 +12,6 @@ using System.Windows.Threading;
using Wabbajack.Common;
using Wabbajack.Common.StatusFeed;
using Wabbajack.Lib;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.NexusApi;
using Wabbajack.Lib.StatusMessages;
namespace Wabbajack
{

View File

@ -8,10 +8,8 @@ using System.Windows;
using System.Windows.Threading;
using ReactiveUI;
using Wabbajack.Common;
using Wabbajack.Common.StatusFeed;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.NexusApi;
using Wabbajack.Lib.StatusMessages;
namespace Wabbajack
{
@ -54,22 +52,10 @@ namespace Wabbajack
MainWindow.ActivePane = oldPane;
}
public void Handle(ConfirmUpdateOfExistingInstall msg)
{
var result = MessageBox.Show(msg.ExtendedDescription, msg.ShortDescription, MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
msg.Confirm();
else
msg.Cancel();
}
public async Task Handle(IUserIntervention msg)
{
switch (msg)
{
case ConfirmUpdateOfExistingInstall c:
Handle(c);
break;
case RequestNexusAuthorization c:
await WrapBrowserJob(msg, async (vm, cancel) =>
{
@ -84,6 +70,8 @@ namespace Wabbajack
c.Resume(data);
});
break;
case ConfirmationIntervention c:
break;
default:
throw new NotImplementedException($"No handler for {msg}");
}

View File

@ -2,6 +2,7 @@
x:Class="Wabbajack.CompilerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:common="clr-namespace:Wabbajack.Common;assembly=Wabbajack.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:local="clr-namespace:Wabbajack"
@ -241,12 +242,29 @@
Margin="5"
Visibility="{Binding Compiling, Converter={StaticResource bool2VisibilityConverter}, FallbackValue=Hidden}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="500" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<local:LogView Grid.Column="0" ProgressPercent="{Binding PercentCompleted, Mode=OneWay}" />
<local:CpuView Grid.Column="2" ProgressPercent="{Binding PercentCompleted, Mode=OneWay}" />
<local:CpuView
Grid.Column="2"
ProgressPercent="{Binding PercentCompleted, Mode=OneWay}"
Visibility="{Binding ActiveGlobalUserIntervention, Converter={StaticResource IsNotNullVisibilityConverter}, ConverterParameter=False}" />
<Border
Grid.Column="2"
Background="{StaticResource SecondaryBackgroundBrush}"
BorderBrush="{StaticResource SecondaryBrush}"
BorderThickness="1"
Visibility="{Binding ActiveGlobalUserIntervention, Converter={StaticResource IsNotNullVisibilityConverter}}">
<ContentPresenter Content="{Binding ActiveGlobalUserIntervention}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type common:ConfirmationIntervention}">
<local:ConfirmationInterventionView />
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
</Grid>
</Grid>
</UserControl>

View File

@ -2,8 +2,10 @@
x:Class="Wabbajack.InstallationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:common="clr-namespace:Wabbajack.Common;assembly=Wabbajack.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:lib="clr-namespace:Wabbajack.Lib;assembly=Wabbajack.Lib"
xmlns:local="clr-namespace:Wabbajack"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignInstance local:InstallerVM}"
@ -344,12 +346,59 @@
Margin="5,0,5,5"
Visibility="{Binding InstallingMode, Converter={StaticResource bool2VisibilityConverter}, FallbackValue=Hidden}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="500" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<local:LogView Grid.Column="0" ProgressPercent="{Binding PercentCompleted, Mode=OneWay}" />
<local:CpuView Grid.Column="2" ProgressPercent="{Binding PercentCompleted, Mode=OneWay}" />
<local:CpuView
Grid.Column="2"
ProgressPercent="{Binding PercentCompleted, Mode=OneWay}"
Visibility="{Binding ActiveGlobalUserIntervention, Converter={StaticResource IsNotNullVisibilityConverter}, ConverterParameter=False}" />
<Border
Grid.Column="2"
Background="{StaticResource SecondaryBackgroundBrush}"
BorderBrush="{StaticResource SecondaryBrush}"
BorderThickness="1"
Visibility="{Binding ActiveGlobalUserIntervention, Converter={StaticResource IsNotNullVisibilityConverter}}">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible, RelativeSource={RelativeSource AncestorType={x:Type Border}}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
AutoReverse="True"
RepeatBehavior="Forever"
Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)"
To="{StaticResource DarkerSecondary}"
Duration="0:0:1.5" />
</Storyboard>
</BeginStoryboard>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
AutoReverse="True"
RepeatBehavior="Forever"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="{StaticResource WindowBackgroundColor}"
Duration="0:0:1.5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<ContentPresenter Content="{Binding ActiveGlobalUserIntervention}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type common:ConfirmationIntervention}">
<local:ConfirmationInterventionView />
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
</Grid>
</Grid>
</UserControl>

View File

@ -0,0 +1,49 @@
<UserControl
x:Class="Wabbajack.ConfirmationInterventionView"
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:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="4*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="3"
Margin="0,0,0,5"
FontFamily="Lucida Sans"
FontSize="14"
FontWeight="Bold"
Text="{Binding ShortDescription}"
TextWrapping="WrapWithOverflow" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
Text="{Binding ExtendedDescription}"
TextWrapping="WrapWithOverflow" />
<Button
Grid.Row="2"
Grid.Column="0"
Command="{Binding CancelCommand}"
Content="Cancel" />
<Button
Grid.Row="2"
Grid.Column="2"
Command="{Binding ConfirmCommand}"
Content="Confirm" />
</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 ConfirmationInterventionView.xaml
/// </summary>
public partial class ConfirmationInterventionView : UserControl
{
public ConfirmationInterventionView()
{
InitializeComponent();
}
}
}

View File

@ -172,6 +172,9 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Views\Interventions\ConfirmationInterventionView.xaml.cs">
<DependentUpon>ConfirmationInterventionView.xaml</DependentUpon>
</Compile>
<Compile Include="Converters\EqualsToBoolConverter.cs" />
<Compile Include="Views\Common\CpuView.xaml.cs">
<DependentUpon>CpuView.xaml</DependentUpon>
@ -259,6 +262,10 @@
<Compile Include="Views\WebBrowserView.xaml.cs">
<DependentUpon>WebBrowserView.xaml</DependentUpon>
</Compile>
<Page Include="Views\Interventions\ConfirmationInterventionView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\Common\CpuView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -529,5 +536,8 @@
<ItemGroup>
<Resource Include="Resources\Wabba_Ded.png" />
</ItemGroup>
<ItemGroup>
<Folder Include="View Models\Installers\User Interventions\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>