Skip to content

Commit

Permalink
Basic exception overlay (#2456)
Browse files Browse the repository at this point in the history
* Basic exception overlay

* Clean up the styling a bit, not perfect but not as bad as it was
  • Loading branch information
halgari authored Jan 13, 2025
1 parent 24847f3 commit 0667217
Show file tree
Hide file tree
Showing 15 changed files with 165 additions and 19 deletions.
7 changes: 7 additions & 0 deletions NexusMods.App.sln
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Networking.Steam.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Abstractions.Hashes", "src\Abstractions\NexusMods.Abstractions.Hashes\NexusMods.Abstractions.Hashes.csproj", "{AF703852-D7B0-4BAD-8C75-B6046C6F0490}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Abstractions.Logging", "src\Abstractions\NexusMods.Abstractions.Logging\NexusMods.Abstractions.Logging.csproj", "{9DE1C2AC-927A-4BC6-B2A1-7016902F8BAE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -752,6 +754,10 @@ Global
{AF703852-D7B0-4BAD-8C75-B6046C6F0490}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF703852-D7B0-4BAD-8C75-B6046C6F0490}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF703852-D7B0-4BAD-8C75-B6046C6F0490}.Release|Any CPU.Build.0 = Release|Any CPU
{9DE1C2AC-927A-4BC6-B2A1-7016902F8BAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9DE1C2AC-927A-4BC6-B2A1-7016902F8BAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9DE1C2AC-927A-4BC6-B2A1-7016902F8BAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9DE1C2AC-927A-4BC6-B2A1-7016902F8BAE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -885,6 +891,7 @@ Global
{24457AAA-8954-4BD6-8EB5-168EAC6EFB1B} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
{17023DB9-8E31-4397-B3E1-141149987865} = {897C4198-884F-448A-B0B0-C2A6D971EAE0}
{AF703852-D7B0-4BAD-8C75-B6046C6F0490} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
{9DE1C2AC-927A-4BC6-B2A1-7016902F8BAE} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9F9F8352-34DD-42C0-8564-EE9AF34A3501}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace NexusMods.Abstractions.Logging;

/// <summary>
/// A global source of exceptions that have been observed by the application.
/// This allows for UI and other systems to tap into log messages.
/// </summary>
public interface IObservableExceptionSource
{
IObservable<LogMessage> Exceptions { get; }
}
12 changes: 12 additions & 0 deletions src/Abstractions/NexusMods.Abstractions.Logging/LogMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace NexusMods.Abstractions.Logging;

/// <summary>
/// A generic log message. Exists as a record to de-couple exceptions and log messages
/// from the backend logging targets
/// </summary>
/// <param name="Exception">The attached Exception (if any)</param>
/// <param name="Message">The log's message</param>
public record LogMessage(Exception? Exception, string Message)
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NLog" />
<PackageReference Include="System.Reactive" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class ApplyControlViewModel : AViewModel<IApplyControlViewModel>, IApplyC
private readonly IJobMonitor _jobMonitor;

private readonly LoadoutId _loadoutId;
private readonly IOverlayController _overlayController;
private readonly IServiceProvider _serviceProvider;
private readonly GameInstallMetadataId _gameMetadataId;
[Reactive] private bool CanApply { get; set; } = true;

Expand All @@ -41,7 +41,7 @@ public class ApplyControlViewModel : AViewModel<IApplyControlViewModel>, IApplyC
public ApplyControlViewModel(LoadoutId loadoutId, IServiceProvider serviceProvider, IJobMonitor jobMonitor, IOverlayController overlayController, GameRunningTracker gameRunningTracker)
{
_loadoutId = loadoutId;
_overlayController = overlayController;
_serviceProvider = serviceProvider;
_syncService = serviceProvider.GetRequiredService<ISynchronizerService>();
_conn = serviceProvider.GetRequiredService<IConnection>();
_jobMonitor = serviceProvider.GetRequiredService<IJobMonitor>();
Expand Down Expand Up @@ -118,7 +118,7 @@ await Task.Run(async () =>
catch (ExecutableInUseException)
{
var marker = NexusMods.Abstractions.Loadouts.Loadout.Load(_conn.Db, _loadoutId);
await MessageBoxOkViewModel.ShowGameAlreadyRunningError(_overlayController, marker.Installation.Name);
await MessageBoxOkViewModel.ShowGameAlreadyRunningError(_serviceProvider, marker.Installation.Name);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,16 @@ public class LaunchButtonViewModel : AViewModel<ILaunchButtonViewModel>, ILaunch
private readonly IToolManager _toolManager;
private readonly IConnection _conn;
private readonly IJobMonitor _monitor;
private readonly IOverlayController _overlayController;
private readonly IServiceProvider _serviceProvider;
private readonly GameRunningTracker _gameRunningTracker;

public LaunchButtonViewModel(ILogger<ILaunchButtonViewModel> logger, IToolManager toolManager, IConnection conn, IJobMonitor monitor, IOverlayController overlayController, GameRunningTracker gameRunningTracker)
public LaunchButtonViewModel(ILogger<ILaunchButtonViewModel> logger, IToolManager toolManager, IConnection conn, IJobMonitor monitor, IServiceProvider serviceProvider, GameRunningTracker gameRunningTracker)
{
_logger = logger;
_toolManager = toolManager;
_conn = conn;
_monitor = monitor;
_overlayController = overlayController;
_serviceProvider = serviceProvider;
_gameRunningTracker = gameRunningTracker;

this.WhenActivated(cd =>
Expand Down Expand Up @@ -72,7 +72,7 @@ await Task.Run(async () =>
}
catch (ExecutableInUseException)
{
await MessageBoxOkViewModel.ShowGameAlreadyRunningError(_overlayController, marker.Installation.Name);
await MessageBoxOkViewModel.ShowGameAlreadyRunningError(_serviceProvider, marker.Installation.Name);
}
catch (Exception ex)
{
Expand Down
1 change: 1 addition & 0 deletions src/NexusMods.App.UI/NexusMods.App.UI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Abstractions\NexusMods.Abstractions.Logging\NexusMods.Abstractions.Logging.csproj" />
<ProjectReference Include="..\Abstractions\NexusMods.Abstractions.NexusModsLibrary\NexusMods.Abstractions.NexusModsLibrary.csproj" />
<ProjectReference Include="..\Abstractions\NexusMods.Abstractions.Resources.Caching\NexusMods.Abstractions.Resources.Caching.csproj" />
<ProjectReference Include="..\Abstractions\NexusMods.Abstractions.Resources.IO\NexusMods.Abstractions.Resources.IO.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using NexusMods.App.UI.Controls.MarkdownRenderer;
using R3;
namespace NexusMods.App.UI.Overlays.Generic.MessageBox.Ok;

Expand All @@ -6,6 +7,19 @@ namespace NexusMods.App.UI.Overlays.Generic.MessageBox.Ok;
/// </summary>
public interface IMessageBoxOkViewModel : IOverlayViewModel<Unit>
{
/// <summary>
/// A short title for the message box.
/// </summary>
public string Title { get; set; }

/// <summary>
/// A description of what's happening.
/// </summary>
public string Description { get; set; }

/// <summary>
/// If provided, this will be displayed in a markdown control below the description. Use this
/// for more descriptive information.
/// </summary>
public IMarkdownRendererViewModel? MarkdownRenderer { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
xmlns:base="clr-namespace:NexusMods.App.UI.Overlays.Generic.MessageBox.Base"
xmlns:resources="clr-namespace:NexusMods.App.UI.Resources">

<base:MessageBoxBackground MinWidth="300" MaxWidth="450">
<base:MessageBoxBackground MinWidth="300" MaxWidth="640">
<base:MessageBoxBackground.TopContent>
<StackPanel Orientation="Vertical" Margin="24">
<StackPanel Orientation="Vertical" Margin="24" Spacing="16">

<!-- Title -->
<DockPanel HorizontalAlignment="Stretch" Margin="0,0,0,16">
Expand All @@ -30,7 +30,10 @@
</DockPanel>

<!-- Message -->
<TextBlock x:Name="MessageTextBlock" TextWrapping="WrapWithOverflow" />
<TextBlock x:Name="MessageTextBlock" TextWrapping="WrapWithOverflow" MaxLines="50" />

<!-- Supporting Markdown -->
<reactiveUi:ViewModelViewHost x:Name="MarkdownRendererViewModelViewHost" MaxHeight="640" MaxWidth="600"/>

</StackPanel>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.ReactiveUI;
using R3;
using ReactiveUI;
Expand All @@ -21,7 +22,15 @@ public MessageBoxOkView()

this.OneWayBind(ViewModel, vm => vm.Description, v => v.MessageTextBlock.Text)
.DisposeWith(disposables);


this.OneWayBind(ViewModel, vm => vm.MarkdownRenderer, v => v.MarkdownRendererViewModelViewHost.ViewModel)
.DisposeWith(disposables);

this.WhenAnyValue(view => view.ViewModel!.MarkdownRenderer)
.Select(vm => vm != null)
.BindTo(this, v => v.MarkdownRendererViewModelViewHost.IsVisible)
.DisposeWith(disposables);

// Bind commands
OkButton.Command = ReactiveCommand.Create(() =>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using NexusMods.App.UI.Controls.MarkdownRenderer;
using NexusMods.App.UI.Resources;
using R3;
using ReactiveUI.Fody.Helpers;
Expand All @@ -11,15 +13,32 @@ public class MessageBoxOkViewModel : AOverlayViewModel<IMessageBoxOkViewModel, U
[Reactive]
public string Description { get; set; } = "This is some very long design only text that spans multiple lines!! This text is super cool!!";

[Reactive] public required IMarkdownRendererViewModel? MarkdownRenderer { get; set; }

/// <summary>
/// Shows the 'Game is already Running' error when you try to synchronize and a game is already running (usually on Windows).
/// </summary>
public static async Task ShowGameAlreadyRunningError(IOverlayController overlayController, string gameName)
public static Task ShowGameAlreadyRunningError(IServiceProvider serviceProvider, string gameName)
{
var viewModel = new MessageBoxOkViewModel()
return Show(serviceProvider, Language.ErrorGameAlreadyRunning_Title, string.Format(Language.ErrorGameAlreadyRunning_Description, gameName));
}

public static async Task Show(IServiceProvider serviceProvider, string title, string description, string? markdown = null)
{
var overlayController = serviceProvider.GetRequiredService<IOverlayController>();

IMarkdownRendererViewModel? markdownRenderer = null;
if (markdown != null)
{
markdownRenderer = serviceProvider.GetRequiredService<IMarkdownRendererViewModel>();
markdownRenderer.Contents = markdown;
}

var viewModel = new MessageBoxOkViewModel
{
Title = Language.ErrorGameAlreadyRunning_Title,
Description = string.Format(Language.ErrorGameAlreadyRunning_Description, gameName) ,
Title = title,
Description = description,
MarkdownRenderer = markdownRenderer,
};
await overlayController.EnqueueAndWait(viewModel);
}
Expand Down
36 changes: 34 additions & 2 deletions src/NexusMods.App.UI/Windows/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Microsoft.Extensions.DependencyInjection;
using NexusMods.Abstractions.Logging;
using NexusMods.Abstractions.NexusWebApi;
using NexusMods.Abstractions.UI;
using NexusMods.App.UI.Controls.DevelopmentBuildBanner;
Expand All @@ -11,6 +12,7 @@
using NexusMods.App.UI.LeftMenu;
using NexusMods.App.UI.Overlays;
using NexusMods.App.UI.Overlays.AlphaWarning;
using NexusMods.App.UI.Overlays.Generic.MessageBox.Ok;
using NexusMods.App.UI.Overlays.Login;
using NexusMods.App.UI.Overlays.MetricsOptIn;
using NexusMods.App.UI.Overlays.Updater;
Expand Down Expand Up @@ -48,9 +50,12 @@ public MainWindowViewModel(

Spine = serviceProvider.GetRequiredService<ISpineViewModel>();
DevelopmentBuildBanner = serviceProvider.GetRequiredService<IDevelopmentBuildBannerViewModel>();

this.WhenActivated(d =>
{
ConnectErrors(serviceProvider)
.DisposeWith(d);

var alphaWarningViewModel = serviceProvider.GetRequiredService<IAlphaWarningViewModel>();
alphaWarningViewModel.WorkspaceController = WorkspaceController;
alphaWarningViewModel.Controller = overlayController;
Expand Down Expand Up @@ -103,7 +108,34 @@ public MainWindowViewModel(
}
});
}


private IDisposable ConnectErrors(IServiceProvider provider)
{
var source = provider.GetService<IObservableExceptionSource>();
if (source is null)
return Disposable.Empty;

return source.Exceptions
.Subscribe(msg =>
{
var title = "Unhandled Exception";
var description = msg.Message;
string? details = null;
if (msg.Exception != null)
{
details = $"""
### Exception Details
```
{msg.Exception}
```
""";
}

Task.Run(() => MessageBoxOkViewModel.Show(provider, title, description, details));
}
);
}

internal void OnClose()
{
// NOTE(erri120): This gets called by the View and can't be inside the disposable
Expand Down
1 change: 1 addition & 0 deletions src/NexusMods.App/NexusMods.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Abstractions\NexusMods.Abstractions.Logging\NexusMods.Abstractions.Logging.csproj" />
<ProjectReference Include="..\ArchiveManagement\NexusMods.FileExtractor\NexusMods.FileExtractor.csproj" />
<ProjectReference Include="..\Games\NexusMods.Games.AdvancedInstaller.UI\NexusMods.Games.AdvancedInstaller.UI.csproj" />
<ProjectReference Include="..\Games\NexusMods.Games.FOMOD.UI\NexusMods.Games.FOMOD.UI.csproj" />
Expand Down
19 changes: 19 additions & 0 deletions src/NexusMods.App/ObservableLoggingTarget.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Reactive.Subjects;
using NexusMods.Abstractions.Logging;
using NLog;
using NLog.Targets;

namespace NexusMods.App;

public class ObservableLoggingTarget : Target, IObservableExceptionSource
{
public IObservable<LogMessage> Exceptions => _exceptions;

private Subject<LogMessage> _exceptions = new();

protected override void Write(LogEventInfo logEvent)
{
if (logEvent.Level == LogLevel.Error || logEvent.Level == LogLevel.Fatal)
_exceptions.OnNext(new LogMessage(logEvent.Exception, logEvent.FormattedMessage));
}
}
9 changes: 7 additions & 2 deletions src/NexusMods.App/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NexusMods.Abstractions.Logging;
using NexusMods.Abstractions.Serialization;
using NexusMods.Abstractions.Settings;
using NexusMods.Abstractions.Telemetry;
Expand Down Expand Up @@ -222,6 +223,7 @@ private static IHost BuildHost(
ExperimentalSettings experimentalSettings,
GameLocatorSettings? gameLocatorSettings = null)
{
var observableTarget = new ObservableLoggingTarget();
var host = new HostBuilder().ConfigureServices(services =>
{
var s = services.AddApp(
Expand All @@ -230,18 +232,20 @@ private static IHost BuildHost(
experimentalSettings: experimentalSettings,
gameLocatorSettings: gameLocatorSettings).Validate();

s.AddSingleton<IObservableExceptionSource, ObservableLoggingTarget>(_ => observableTarget);

if (startupMode.IsAvaloniaDesigner)
{
s.OverrideSettingsForTests<DataModelSettings>(settings => settings with { UseInMemoryDataModel = true, });
}
})
.ConfigureLogging((_, builder) => AddLogging(builder, loggingSettings, startupMode))
.ConfigureLogging((_, builder) => AddLogging(observableTarget, builder, loggingSettings, startupMode))
.Build();

return host;
}

private static void AddLogging(ILoggingBuilder loggingBuilder, LoggingSettings settings, StartupMode startupMode)
private static void AddLogging(ObservableLoggingTarget observableLoggingTarget, ILoggingBuilder loggingBuilder, LoggingSettings settings, StartupMode startupMode)
{
var fs = FileSystem.Shared;
var config = new NLog.Config.LoggingConfiguration();
Expand Down Expand Up @@ -282,6 +286,7 @@ private static void AddLogging(ILoggingBuilder loggingBuilder, LoggingSettings s
}

config.AddRuleForAllLevels(fileTarget);
config.AddRuleForAllLevels(observableLoggingTarget);


// NOTE(erri120): RemoveLoggerFactoryFilter prevents
Expand Down

0 comments on commit 0667217

Please sign in to comment.