Merge pull request #303 from Noggog/bugs-n-features

Bugs n features
This commit is contained in:
Timothy Baldridge 2019-12-22 13:57:43 -08:00 committed by GitHub
commit 00f702f1f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 356 additions and 74 deletions

View File

@ -111,13 +111,13 @@ namespace Wabbajack.CacheServer
public static void Start()
{
new Thread(() =>
Task.Run(async () =>
{
while (true)
{
try
{
ValidateLists().Wait();
await ValidateLists();
}
catch (Exception ex)
{
@ -125,9 +125,9 @@ namespace Wabbajack.CacheServer
}
// Sleep for two hours
Thread.Sleep(1000 * 60 * 60 * 2);
await Task.Delay(1000 * 60 * 60 * 2);
}
}).Start();
}).FireAndForget();
}
public static async Task ValidateLists()
{

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Wabbajack.Common
{
public class AsyncLock
{
private SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
public async Task<IDisposable> Wait()
{
await _lock.WaitAsync();
return Disposable.Create(() => _lock.Release());
}
}
}

View File

@ -1011,6 +1011,11 @@ namespace Wabbajack.Common
p.WaitForExitAndWarn(TimeSpan.FromSeconds(30), $"Deletion process of {path}");
}
public static bool IsUnderneathDirectory(string path, string dirPath)
{
return path.StartsWith(dirPath, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Writes a file to JSON but in an encrypted format in the user's app local directory.
/// The data will be encrypted so that it can only be read by this machine and this user.

View File

@ -130,6 +130,7 @@
<Compile Include="StatusUpdate.cs" />
<Compile Include="SteamHandler.cs" />
<Compile Include="Utils.cs" />
<Compile Include="Util\AsyncLock.cs" />
<Compile Include="Util\TempFile.cs" />
<Compile Include="Util\TempFolder.cs" />
<Compile Include="WorkQueue.cs" />

View File

@ -75,8 +75,9 @@ namespace Wabbajack.Lib.Downloaders
public async Task<bool> DoDownload(Archive a, string destination, bool download)
{
if (download && !Directory.Exists(Directory.GetParent(destination).FullName))
Directory.CreateDirectory(Directory.GetParent(destination).FullName);
var parent = Directory.GetParent(destination);
if (download && !Directory.Exists(parent.FullName))
Directory.CreateDirectory(parent.FullName);
using (var fs = download ? File.OpenWrite(destination) : null)
{
@ -120,12 +121,6 @@ namespace Wabbajack.Lib.Downloaders
var contentSize = headerVar != null ? long.Parse(headerVar) : 1;
FileInfo fileInfo = new FileInfo(destination);
if (!fileInfo.Directory.Exists)
{
Directory.CreateDirectory(fileInfo.Directory.FullName);
}
using (var webs = stream)
{
var buffer = new byte[bufferSize];

View File

@ -17,6 +17,7 @@ namespace Wabbajack.Lib.Downloaders
private FileSystemWatcher _watcher;
private Subject<FileEvent> _fileEvents = new Subject<FileEvent>();
private KnownFolder _downloadfolder;
public readonly AsyncLock Lock = new AsyncLock();
class FileEvent
{
@ -81,7 +82,7 @@ namespace Wabbajack.Lib.Downloaders
{
var downloader = (ManualDownloader)GetDownloader();
var absPath = Path.Combine(downloader._downloadfolder.Path, a.Name);
lock (downloader)
using (await downloader.Lock.Wait())
{
try
{
@ -95,8 +96,8 @@ namespace Wabbajack.Lib.Downloaders
.Select(x => x.FirstOrDefault())
.FirstOrDefaultAsync();
Process.Start(Url);
absPath = watcher.Wait()?.FullPath;
absPath = (await watcher)?.FullPath;
if (!File.Exists(absPath))
throw new InvalidDataException($"File not found after manual download operation");
File.Move(absPath, destination);

View File

@ -319,7 +319,7 @@ namespace Wabbajack.Lib
File.WriteAllText(Path.Combine(OutputFolder, directive.To), data);
}
public static IErrorResponse CheckValidInstallPath(string path)
public static IErrorResponse CheckValidInstallPath(string path, string downloadFolder)
{
var ret = Utils.IsDirectoryPathValid(path);
if (!ret.Succeeded) return ret;
@ -339,8 +339,8 @@ namespace Wabbajack.Lib
// Check folder is either empty, or a likely valid previous install
if (!Directory.IsEmpty(path))
{
// Some probably naive check, but should be a good starting point to improve later
if (!Directory.EnumerateFiles(path).Any(file =>
// If we have a MO2 install, assume good to go
if (Directory.EnumerateFiles(path).Any(file =>
{
var fileName = Path.GetFileName(file);
if (fileName.Equals("ModOrganizer.exe", StringComparison.OrdinalIgnoreCase)) return true;
@ -348,7 +348,19 @@ namespace Wabbajack.Lib
return false;
}))
{
return ErrorResponse.Fail($"Cannot install into a non-empty folder that does not look like a previous WJ installation.");
return ErrorResponse.Success;
}
// If we don't have a MO2 install, and there's any file that's not in the downloads folder, mark failure
if (Directory.EnumerateFiles(path).Any(file =>
{
var fileName = Path.GetFileName(file);
if (string.IsNullOrWhiteSpace(downloadFolder)) return true;
return !Utils.IsUnderneathDirectory(file, downloadFolder);
}))
{
return ErrorResponse.Fail($"Cannot install into a non-empty folder that does not look like a previous WJ installation.\n" +
$"To override, delete all installed files from your target installation folder. Any files in your download folder are okay to keep.");
}
}

View File

@ -62,12 +62,10 @@ namespace Wabbajack.Lib.NexusApi
public async Task<string> Username() => (await UserStatus).name;
private static SemaphoreSlim _getAPIKeyLock = new SemaphoreSlim(1, 1);
private static AsyncLock _getAPIKeyLock = new AsyncLock();
private static async Task<string> GetApiKey()
{
await _getAPIKeyLock.WaitAsync();
try
using (await _getAPIKeyLock.Wait())
{
// Clean up old location
if (File.Exists(API_KEY_CACHE_FILE))
@ -91,10 +89,6 @@ namespace Wabbajack.Lib.NexusApi
return await RequestAndCacheAPIKey();
}
finally
{
_getAPIKeyLock.Release();
}
}
public static async Task<string> RequestAndCacheAPIKey()

View File

@ -45,11 +45,11 @@ namespace Wabbajack.Test
protected async Task<ModList> CompileAndInstall(string profile)
{
var compiler = await ConfigureAndRunCompiler(profile);
Install(compiler);
await Install(compiler);
return compiler.ModList;
}
protected void Install(MO2Compiler compiler)
protected async Task Install(MO2Compiler compiler)
{
var modlist = AInstaller.LoadFromFile(compiler.ModListOutputFile);
var installer = new MO2Installer(
@ -59,7 +59,7 @@ namespace Wabbajack.Test
downloadFolder: utils.DownloadsFolder);
installer.WarnOnOverwrite = false;
installer.GameFolder = utils.GameFolder;
installer.Begin().Wait();
await installer.Begin();
}
}
}

View File

@ -56,11 +56,11 @@ namespace Wabbajack.Test
protected async Task<ModList> CompileAndInstall()
{
var vortexCompiler = await ConfigureAndRunCompiler();
Install(vortexCompiler);
await Install(vortexCompiler);
return vortexCompiler.ModList;
}
protected void Install(VortexCompiler vortexCompiler)
protected async Task Install(VortexCompiler vortexCompiler)
{
var modList = AInstaller.LoadFromFile(vortexCompiler.ModListOutputFile);
var installer = new MO2Installer(
@ -71,7 +71,7 @@ namespace Wabbajack.Test
{
GameFolder = utils.GameFolder,
};
installer.Begin().Wait();
await installer.Begin();
}
}
}

View File

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Wabbajack.Common;
namespace Wabbajack.Test
{
[TestClass]
public class AsyncLockTests
{
[TestMethod]
public async Task Typical()
{
var asyncLock = new AsyncLock();
bool firstRun = false;
var first = Task.Run(async () =>
{
using (await asyncLock.Wait())
{
await Task.Delay(500);
firstRun = true;
}
});
var second = Task.Run(async () =>
{
await Task.Delay(200);
using (await asyncLock.Wait())
{
Assert.IsTrue(firstRun);
}
});
await Task.WhenAll(first, second);
}
[TestMethod]
public async Task Exception()
{
var asyncLock = new AsyncLock();
bool firstRun = false;
bool secondRun = false;
// Throw exception inside a lock
await Assert.ThrowsExceptionAsync<Exception>(() =>
{
return Task.Run(async () =>
{
using (await asyncLock.Wait())
{
await Task.Delay(500);
firstRun = true;
throw new Exception();
}
});
});
await Task.WhenAll(
// Try to re-enter lock afterwards
Task.Run(async () =>
{
await Task.Delay(200);
using (await asyncLock.Wait())
{
Assert.IsTrue(firstRun);
secondRun = true;
}
}),
// Add a timeout to fail if we cannot
Task.Run(async () =>
{
await Task.Delay(500);
if (!secondRun)
{
throw new ArgumentException();
}
}));
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Wabbajack.Common;
using Wabbajack.Lib;
namespace Wabbajack.Test
{
[TestClass]
public class MO2Tests
{
#region CheckValidInstallPath
[TestMethod]
public void CheckValidInstallPath_Empty()
{
using (var tempDir = new TempFolder())
{
Assert.IsTrue(MO2Installer.CheckValidInstallPath(tempDir.Dir.FullName, downloadFolder: null).Succeeded);
}
}
[TestMethod]
public void CheckValidInstallPath_DoesNotExist()
{
using (var tempDir = new TempFolder())
{
Assert.IsTrue(MO2Installer.CheckValidInstallPath(Path.Combine(tempDir.Dir.FullName, "Subfolder"), downloadFolder: null).Succeeded);
}
}
[TestMethod]
public void CheckValidInstallPath_Invalid()
{
using (var tempDir = new TempFolder())
{
Assert.IsFalse(MO2Installer.CheckValidInstallPath($"{tempDir.Dir.FullName}/*", downloadFolder: null).Succeeded);
}
}
[TestMethod]
public void CheckValidInstallPath_HasModlist()
{
using (var tempDir = new TempFolder())
{
File.Create(Path.Combine(tempDir.Dir.FullName, $"ModOrganizer.exe"));
File.Create(Path.Combine(tempDir.Dir.FullName, $"modlist{ExtensionManager.Extension}"));
Assert.IsFalse(MO2Installer.CheckValidInstallPath(tempDir.Dir.FullName, downloadFolder: null).Succeeded);
}
}
[TestMethod]
public void CheckValidInstallPath_ProperOverwrite()
{
using (var tempDir = new TempFolder())
{
File.Create(Path.Combine(tempDir.Dir.FullName, $"ModOrganizer.exe"));
Assert.IsTrue(MO2Installer.CheckValidInstallPath(tempDir.Dir.FullName, downloadFolder: null).Succeeded);
}
}
[TestMethod]
public void CheckValidInstallPath_ImproperOverwrite()
{
using (var tempDir = new TempFolder())
{
File.Create(Path.Combine(tempDir.Dir.FullName, $"someFile.txt"));
Assert.IsFalse(MO2Installer.CheckValidInstallPath(tempDir.Dir.FullName, downloadFolder: null).Succeeded);
}
}
[TestMethod]
public void CheckValidInstallPath_OverwriteFilesInDownloads()
{
using (var tempDir = new TempFolder())
{
var downloadsFolder = Path.Combine(tempDir.Dir.FullName, "downloads");
Directory.CreateDirectory(downloadsFolder);
File.Create(Path.Combine(tempDir.Dir.FullName, $"downloads/someFile.txt"));
Assert.IsFalse(MO2Installer.CheckValidInstallPath(tempDir.Dir.FullName, downloadFolder: downloadsFolder).Succeeded);
}
}
#endregion
}
}

View File

@ -99,6 +99,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ACompilerTest.cs" />
<Compile Include="AsyncLockTests.cs" />
<Compile Include="AVortexCompilerTest.cs" />
<Compile Include="CSP\ChannelTests.cs" />
<Compile Include="CSP\CSPTests.cs" />
@ -107,6 +108,7 @@
<Compile Include="Extensions.cs" />
<Compile Include="FilePickerTests.cs" />
<Compile Include="MiscTests.cs" />
<Compile Include="MO2Tests.cs" />
<Compile Include="ModlistMetadataTests.cs" />
<Compile Include="RestartingDownloadsTests.cs" />
<Compile Include="SimpleHTTPServer.cs" />

View File

@ -41,7 +41,8 @@
<Color x:Key="Primary">#BB86FC</Color>
<Color x:Key="PrimaryTransparent">#00BB86FC</Color>
<Color x:Key="PrimaryVariant">#3700B3</Color>
<Color x:Key="DarkPrimaryVariant">#1b0059</Color>
<Color x:Key="DarkPrimaryVariant">#270080</Color>
<Color x:Key="DarkerPrimaryVariant">#1b0059</Color>
<Color x:Key="Secondary">#03DAC6</Color>
<Color x:Key="DarkSecondary">#0e8f83</Color>
<Color x:Key="DarkerSecondary">#095952</Color>
@ -117,6 +118,7 @@
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}" />
<SolidColorBrush x:Key="PrimaryVariantBrush" Color="{StaticResource PrimaryVariant}" />
<SolidColorBrush x:Key="DarkPrimaryVariantBrush" Color="{StaticResource DarkPrimaryVariant}" />
<SolidColorBrush x:Key="DarkerPrimaryVariantBrush" Color="{StaticResource DarkerPrimaryVariant}" />
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}" />
<SolidColorBrush x:Key="DarkSecondaryBrush" Color="{StaticResource DarkSecondary}" />
<SolidColorBrush x:Key="DarkerSecondaryBrush" Color="{StaticResource DarkerSecondary}" />

View File

@ -47,8 +47,6 @@ namespace Wabbajack
PathType = FilePickerVM.PathTypeOptions.Folder,
PromptTitle = "Select Installation Directory",
};
Location.AdditionalError = this.WhenAny(x => x.Location.TargetPath)
.Select(x => MO2Installer.CheckValidInstallPath(x));
DownloadLocation = new FilePickerVM()
{
ExistCheckOption = FilePickerVM.CheckOptions.Off,
@ -57,6 +55,13 @@ namespace Wabbajack
};
DownloadLocation.AdditionalError = this.WhenAny(x => x.DownloadLocation.TargetPath)
.Select(x => Utils.IsDirectoryPathValid(x));
Location.AdditionalError = Observable.CombineLatest(
this.WhenAny(x => x.Location.TargetPath),
this.WhenAny(x => x.DownloadLocation.TargetPath),
resultSelector: (target, download) =>
{
return MO2Installer.CheckValidInstallPath(target, download);
});
CanInstall = Observable.CombineLatest(
this.WhenAny(x => x.Location.InError),
@ -83,7 +88,7 @@ namespace Wabbajack
_CurrentSettings = installerVM.WhenAny(x => x.ModListLocation.TargetPath)
.Select(path => path == null ? null : installerVM.MWVM.Settings.Installer.Mo2ModlistSettings.TryCreate(path))
.ToProperty(this, nameof(CurrentSettings));
(this).WhenAny(x => x.CurrentSettings)
this.WhenAny(x => x.CurrentSettings)
.Pairwise()
.Subscribe(settingsPair =>
{

View File

@ -49,7 +49,8 @@ namespace Wabbajack
try
{
var ret = new MemoryStream();
using (Stream stream = await new HttpClient().GetStreamAsync(url))
using (var client = new HttpClient())
using (Stream stream = await client.GetStreamAsync(url))
{
stream.CopyTo(ret);
}
@ -82,7 +83,7 @@ namespace Wabbajack
}
})
.Replay(1)
.RefCount();
.RefCount(TimeSpan.FromMilliseconds(5000));
}
}
}

View File

@ -5,8 +5,10 @@ using System;
using System.Diagnostics;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Windows.Media.Imaging;
using Wabbajack.Common;
using Wabbajack.Lib;
using Wabbajack.Lib.Downloaders;
@ -33,6 +35,8 @@ namespace Wabbajack
public IReactiveCommand SlideShowNextItemCommand { get; } = ReactiveCommand.Create(() => { });
public IReactiveCommand VisitNexusSiteCommand { get; }
public const int PreloadAmount = 4;
public SlideShow(InstallerVM appState)
{
Installer = appState;
@ -98,7 +102,11 @@ namespace Wabbajack
_targetMod = Observable.CombineLatest(
modVMs.QueryWhenChanged(),
selectedIndex,
resultSelector: (query, selected) => query.Items.ElementAtOrDefault(selected % (query.Count == 0 ? 1 : query.Count)))
resultSelector: (query, selected) =>
{
var index = selected % (query.Count == 0 ? 1 : query.Count);
return query.Items.ElementAtOrDefault(index);
})
.StartWith(default(ModVM))
.ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, nameof(TargetMod));
@ -116,14 +124,22 @@ namespace Wabbajack
.Select(x => x?.StartsWith("https://") ?? false)
.ObserveOnGuiThread());
// ToDo
// Can maybe add "preload" systems to prep upcoming images
// This would entail subscribing to modVMs, narrowing it down to Top(X) or Page() somehow.
// The result would not be used anywhere, just simply expressing interest in those mods'
// images will implicitly cache them
//
// Page would be really clever to use, but it's not exactly right as its "window" won't follow the current index,
// so at the boundary of a page, the next image won't be cached. Need like a Page() /w an offset parameter, or something.
// Preload upcoming images
var list = Observable.CombineLatest(
modVMs.QueryWhenChanged(),
selectedIndex,
resultSelector: (query, selected) =>
{
// Retrieve the mods that should be preloaded
var index = selected % (query.Count == 0 ? 1 : query.Count);
var amountToTake = Math.Min(query.Count - index, PreloadAmount);
return query.Items.Skip(index).Take(amountToTake).ToObservable();
})
.Select(i => i.ToObservableChangeSet())
.Switch()
.Transform(mod => mod.ImageObservable.Subscribe())
.DisposeMany()
.AsObservableList();
}
}
}

View File

@ -12,32 +12,35 @@
<Grid>
<Rectangle Fill="{StaticResource HeatedBorderBrush}" Opacity="{Binding ProgressPercent, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<ListBox
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
BorderBrush="Transparent"
BorderThickness="1"
ItemsSource="{Binding StatusList}">
ItemsSource="{Binding StatusList}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="500" />
</Grid.ColumnDefinitions>
<Grid Background="{StaticResource WindowBackgroundBrush}">
<mahapps:MetroProgressBar
Grid.Column="0"
Background="{StaticResource WindowBackgroundBrush}"
BorderThickness="0"
Foreground="Transparent"
Maximum="1"
Value="{Binding Status.ProgressPercent, Mode=OneWay}" />
<mahapps:MetroProgressBar
Grid.Column="0"
Background="Transparent"
BorderThickness="0"
Foreground="{StaticResource PrimaryVariantBrush}"
Foreground="{StaticResource DarkPrimaryVariantBrush}"
Maximum="1"
Opacity="{Binding Status.ProgressPercent, Mode=OneWay}"
Value="{Binding Status.ProgressPercent, Mode=OneWay}" />
<TextBlock Grid.Column="0" Text="{Binding Status.Msg}" />
<Grid Height="1" VerticalAlignment="Bottom">
<mahapps:MetroProgressBar
Background="Transparent"
BorderThickness="0"
Foreground="{StaticResource DarkSecondaryBrush}"
Maximum="1"
Value="{Binding Status.ProgressPercent, Mode=OneWay}" />
</Grid>
<TextBlock
Margin="0,0,0,2"
Text="{Binding Status.Msg}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"
ToolTip="{Binding Status.Msg}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>

View File

@ -1,4 +1,4 @@
<local:UserControlRx
<local:UserControlRx
x:Class="Wabbajack.DetailImageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@ -88,8 +88,8 @@
x:Name="TitleTextShadow"
Grid.Row="2"
Grid.Column="0"
Margin="-20,15,40,-10"
Padding="40,10"
Margin="-20,5,-20,-20"
Padding="40,20"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
FontFamily="Lucida Sans"

View File

@ -72,7 +72,7 @@
Value="{Binding ProgressPercent, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}">
<mahapps:MetroProgressBar.Foreground>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="0" Color="{StaticResource DarkPrimaryVariant}" />
<GradientStop Offset="0" Color="{StaticResource DarkerPrimaryVariant}" />
<GradientStop Offset="0.5" Color="{StaticResource PrimaryVariant}" />
</LinearGradientBrush>
</mahapps:MetroProgressBar.Foreground>

View File

@ -3,6 +3,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:icon="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:local="clr-namespace:Wabbajack"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="250"
@ -11,7 +12,7 @@
<UserControl.Resources>
<Color x:Key="YellowColor">#fec701</Color>
<SolidColorBrush x:Key="YellowBrush" Color="{StaticResource YellowColor}" />
<Color x:Key="TransparentColor">#99000000</Color>
<Color x:Key="TransparentColor">#AA000000</Color>
<SolidColorBrush x:Key="TransparentBrush" Color="{StaticResource TransparentColor}" />
<LinearGradientBrush x:Key="LinesBrush" MappingMode="Absolute" SpreadMethod="Repeat" StartPoint="0,0" EndPoint="48,36">
<GradientStop Offset="0" Color="Black" />
@ -36,10 +37,36 @@
ShadowDepth="10" />
</Rectangle.Effect>
</Rectangle>
<Grid
Grid.Row="1"
Margin="30"
Visibility="{Binding ShowHelp, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource bool2VisibilityConverter}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Margin="0,0,0,10"
FontFamily="Lucida Sans"
FontSize="30"
FontWeight="Bold"
Foreground="{StaticResource YellowBrush}"
Text="UNDER MAINTENANCE" />
<TextBlock
Grid.Row="1"
FontFamily="Lucida Sans"
FontSize="15"
FontWeight="Bold"
Foreground="{StaticResource YellowBrush}"
Text="When mods are updated and the old versions are taken down, Wabbajack can no longer install the modlist for you. &#x0a; &#x0a;Modlist authors need to download the new mod and confirm its correctness before the modlist can be made available again."
TextWrapping="WrapWithOverflow" />
</Grid>
<Viewbox
Grid.Row="1"
Margin="20"
VerticalAlignment="Top">
VerticalAlignment="Top"
Visibility="{Binding ShowHelp, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Converter={StaticResource bool2VisibilityConverter}, ConverterParameter=False}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@ -51,7 +78,6 @@
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="1"
FontFamily="Lucida Sans"
FontSize="100"
FontWeight="Bold"
@ -64,9 +90,26 @@
ShadowDepth="10" />
</TextBlock.Effect>
</TextBlock>
<Button
Grid.Row="0"
Width="45"
Height="45"
Margin="0,10"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Click="Help_Click">
<Button.Style>
<Style BasedOn="{StaticResource IconBareButtonStyle}" TargetType="Button">
<Setter Property="Foreground" Value="{StaticResource YellowBrush}" />
</Style>
</Button.Style>
<icon:PackIconMaterial
Width="35"
Height="35"
Kind="HelpCircle" />
</Button>
<TextBlock
Grid.Row="1"
Grid.Column="1"
FontFamily="Lucida Sans"
FontSize="100"
FontWeight="Bold"

View File

@ -20,9 +20,22 @@ namespace Wabbajack
/// </summary>
public partial class UnderMaintenanceOverlay : UserControl
{
public bool ShowHelp
{
get => (bool)GetValue(ShowHelpProperty);
set => SetValue(ShowHelpProperty, value);
}
public static readonly DependencyProperty ShowHelpProperty = DependencyProperty.Register(nameof(ShowHelp), typeof(bool), typeof(UnderMaintenanceOverlay),
new FrameworkPropertyMetadata(default(bool)));
public UnderMaintenanceOverlay()
{
InitializeComponent();
}
private void Help_Click(object sender, RoutedEventArgs e)
{
ShowHelp = !ShowHelp;
}
}
}

View File

@ -176,7 +176,7 @@
<DependentUpon>AttentionBorder.xaml</DependentUpon>
</Compile>
<Compile Include="Converters\IsTypeVisibilityConverter.cs" />
<Compile Include="UnderMaintenanceOverlay.xaml.cs">
<Compile Include="Views\Common\UnderMaintenanceOverlay.xaml.cs">
<DependentUpon>UnderMaintenanceOverlay.xaml</DependentUpon>
</Compile>
<Compile Include="UserInterventions\ShowLoginManager.cs" />
@ -286,7 +286,7 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="UnderMaintenanceOverlay.xaml">
<Page Include="Views\Common\UnderMaintenanceOverlay.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>