mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
UI - Implemented exception dialog
UI - Fixed notification opacity UI - Added position control to notifications
This commit is contained in:
parent
b963aa0909
commit
63eb0ca9b3
@ -491,15 +491,13 @@
|
||||
Represents the base class for Artemis view models used to drive dialogs
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Artemis.UI.Avalonia.Shared.DialogViewModelBase`1.#ctor">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:Artemis.UI.Avalonia.Shared.DialogViewModelBase`1.Close">
|
||||
<member name="M:Artemis.UI.Avalonia.Shared.DialogViewModelBase`1.Close(`0)">
|
||||
<summary>
|
||||
Closes the dialog with a given result
|
||||
Closes the dialog with the given <paramref name="result" />
|
||||
</summary>
|
||||
<param name="result">The result of the dialog</param>
|
||||
</member>
|
||||
<member name="P:Artemis.UI.Avalonia.Shared.DialogViewModelBase`1.Cancel">
|
||||
<member name="M:Artemis.UI.Avalonia.Shared.DialogViewModelBase`1.Cancel">
|
||||
<summary>
|
||||
Closes the dialog without a result
|
||||
</summary>
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Shared.Events
|
||||
{
|
||||
internal class DialogClosedEventArgs<TResult> : EventArgs
|
||||
{
|
||||
public TResult Result { get; }
|
||||
|
||||
public DialogClosedEventArgs(TResult result)
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ReactiveUI;
|
||||
@ -17,7 +18,12 @@ namespace Artemis.UI.Avalonia.Shared.Services.Builders
|
||||
public NotificationBuilder(Window parent)
|
||||
{
|
||||
_parent = parent;
|
||||
_infoBar = new InfoBar {Classes = Classes.Parse("notification-info-bar")};
|
||||
_infoBar = new InfoBar
|
||||
{
|
||||
Classes = Classes.Parse("notification-info-bar"),
|
||||
VerticalAlignment = VerticalAlignment.Bottom,
|
||||
HorizontalAlignment = HorizontalAlignment.Right
|
||||
};
|
||||
}
|
||||
|
||||
public NotificationBuilder WithTitle(string? title)
|
||||
@ -38,6 +44,17 @@ namespace Artemis.UI.Avalonia.Shared.Services.Builders
|
||||
return this;
|
||||
}
|
||||
|
||||
public NotificationBuilder WithVerticalPosition(VerticalAlignment position)
|
||||
{
|
||||
_infoBar.VerticalAlignment = position;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NotificationBuilder WithHorizontalPosition(HorizontalAlignment position)
|
||||
{
|
||||
_infoBar.HorizontalAlignment = position;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a filter to the dialog
|
||||
|
||||
@ -2,8 +2,47 @@
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
|
||||
x:Class="Artemis.UI.Avalonia.Shared.Services.ExceptionDialogView"
|
||||
Title="ExceptionDialogView">
|
||||
Eh you got an exception but I didn't write the viewer yet :(
|
||||
Title="{Binding Title}"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
Width="800"
|
||||
Height="800"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid>
|
||||
<TextBlock Margin="10" IsHitTestVisible="False" Text="{Binding Title}" />
|
||||
<Grid Margin="0 32 0 0" ColumnDefinitions="*,Auto" RowDefinitions="*,Auto">
|
||||
|
||||
<Border Classes="card" Grid.Row="0" Grid.ColumnSpan="2" Margin="10">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Margin="0 5">
|
||||
<Grid RowDefinitions="Auto,Auto,*">
|
||||
<TextBlock Grid.Row="0" Classes="h3">Awww :(</TextBlock>
|
||||
<TextBlock Grid.Row="1">
|
||||
It looks like Artemis ran into an unhandled exception. If this keeps happening feel free to hit us up on Discord.
|
||||
</TextBlock>
|
||||
|
||||
<TextBox Grid.Row="2" Text="{Binding Exception, Mode=OneTime}"
|
||||
Margin="0 10"
|
||||
AcceptsReturn="True"
|
||||
IsReadOnly="True"
|
||||
FontFamily="Consolas"
|
||||
FontSize="12"
|
||||
BorderThickness="0" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" TextWrapping="Wrap" Margin="15" VerticalAlignment="Center">
|
||||
When reporting errors please don't take a screenshot of the error, instead copy the text, thanks!
|
||||
</TextBlock>
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="15">
|
||||
<Button Command="{Binding CopyException}" Classes="AppBarButton" Width="150" Margin="0 0 5 0">
|
||||
Copy exception
|
||||
</Button>
|
||||
<Button Command="{Binding Close}" Width="150" Margin="5 0 0 0">
|
||||
Close
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
@ -4,7 +4,7 @@ using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Shared.Services
|
||||
{
|
||||
public partial class ExceptionDialogView : ReactiveWindow<ExceptionDialogViewModel>
|
||||
internal class ExceptionDialogView : ReactiveWindow<ExceptionDialogViewModel>
|
||||
{
|
||||
public ExceptionDialogView()
|
||||
{
|
||||
|
||||
@ -1,12 +1,35 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Avalonia.Shared.Services.Builders;
|
||||
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
|
||||
using Avalonia;
|
||||
using Avalonia.Layout;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Shared.Services
|
||||
{
|
||||
public class ExceptionDialogViewModel : DialogViewModelBase<object>
|
||||
{
|
||||
public ExceptionDialogViewModel(string title, Exception exception)
|
||||
internal class ExceptionDialogViewModel : DialogViewModelBase<object>
|
||||
{
|
||||
private readonly INotificationService _notificationService;
|
||||
|
||||
public ExceptionDialogViewModel(string title, Exception exception, INotificationService notificationService)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
|
||||
Title = $"Artemis | {title}";
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
public string Title { get; }
|
||||
public Exception Exception { get; }
|
||||
|
||||
public async Task CopyException()
|
||||
{
|
||||
await Application.Current.Clipboard.SetTextAsync(Exception.ToString());
|
||||
_notificationService.CreateNotification()
|
||||
.WithMessage("Copied stack trace to clipboard.")
|
||||
.WithSeverity(NotificationSeverity.Success)
|
||||
.WithHorizontalPosition(HorizontalAlignment.Center)
|
||||
.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Avalonia.Shared.Exceptions;
|
||||
using Artemis.UI.Avalonia.Shared.Services.Builders;
|
||||
@ -7,9 +8,11 @@ using Artemis.UI.Avalonia.Shared.Services.Interfaces;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ninject;
|
||||
using Ninject.Parameters;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Shared.Services
|
||||
{
|
||||
@ -91,25 +94,30 @@ namespace Artemis.UI.Avalonia.Shared.Services
|
||||
|
||||
Window window = (Window) Activator.CreateInstance(type)!;
|
||||
window.DataContext = viewModel;
|
||||
viewModel.CloseRequested += (_, args) => window.Close(args.Result);
|
||||
viewModel.CancelRequested += (_, _) => window.Close();
|
||||
|
||||
return await window.ShowDialog<TResult>(parent);
|
||||
}
|
||||
|
||||
public void ShowExceptionDialog(string title, Exception exception)
|
||||
{
|
||||
if (_exceptionDialogOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_exceptionDialogOpen = true;
|
||||
// Fire and forget the dialog
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_exceptionDialogOpen = true;
|
||||
ShowDialogAsync(new ExceptionDialogViewModel(title, exception)).GetAwaiter().GetResult();
|
||||
await ShowDialogAsync(new ExceptionDialogViewModel(title, exception, _kernel.Get<INotificationService>()));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_exceptionDialogOpen = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ContentDialogBuilder CreateContentDialog()
|
||||
|
||||
@ -13,9 +13,6 @@
|
||||
<Setter Property="Opacity" Value="0" />
|
||||
<Setter Property="MaxWidth" Value="600" />
|
||||
<Setter Property="Margin" Value="15"/>
|
||||
<Setter Property="Background" Value="#25a3a3a3"/>
|
||||
<Setter Property="VerticalAlignment" Value="Bottom" />
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
<Setter Property="Transitions">
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Opacity" Duration="0:0:0.2"/>
|
||||
@ -25,4 +22,7 @@
|
||||
<Style Selector="controls|InfoBar.notification-info-bar[IsOpen=True]">
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
</Style>
|
||||
<Style Selector="controls|InfoBar.notification-info-bar:informational /template/ Border#ContentRoot">
|
||||
<Setter Property="Background" Value="#ff3c3c3c" />
|
||||
</Style>
|
||||
</Styles>
|
||||
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using Artemis.UI.Avalonia.Shared.Events;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Avalonia.Shared
|
||||
@ -60,21 +60,24 @@ namespace Artemis.UI.Avalonia.Shared
|
||||
/// </summary>
|
||||
public abstract class DialogViewModelBase<TResult> : ActivatableViewModelBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected DialogViewModelBase()
|
||||
{
|
||||
Close = ReactiveCommand.Create<TResult, TResult>(t => t);
|
||||
Cancel = ReactiveCommand.Create(() => { });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the dialog with a given result
|
||||
/// Closes the dialog with the given <paramref name="result" />
|
||||
/// </summary>
|
||||
public ReactiveCommand<TResult, TResult> Close { get; }
|
||||
/// <param name="result">The result of the dialog</param>
|
||||
public void Close(TResult result)
|
||||
{
|
||||
CloseRequested?.Invoke(this, new DialogClosedEventArgs<TResult>(result));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the dialog without a result
|
||||
/// </summary>
|
||||
public ReactiveCommand<Unit, Unit> Cancel { get; }
|
||||
public void Cancel()
|
||||
{
|
||||
CancelRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
internal event EventHandler<DialogClosedEventArgs<TResult>>? CloseRequested;
|
||||
internal event EventHandler? CancelRequested;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@ -118,7 +118,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
|
||||
|
||||
public void Accept()
|
||||
{
|
||||
Close.Execute(true);
|
||||
Close(true);
|
||||
}
|
||||
|
||||
public static async Task<bool> Show(IWindowService windowService, List<IPrerequisitesSubject> subjects)
|
||||
|
||||
@ -105,7 +105,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
|
||||
|
||||
// This shouldn't be happening and the experience isn't very nice for the user (too lazy to make a nice UI for such an edge case)
|
||||
// but at least give some feedback
|
||||
Close.Execute(false);
|
||||
Close(false);
|
||||
await _windowService.CreateContentDialog()
|
||||
.WithTitle("Plugin prerequisites")
|
||||
.WithContent("The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.")
|
||||
@ -126,7 +126,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
|
||||
|
||||
public void Accept()
|
||||
{
|
||||
Close.Execute(true);
|
||||
Close(true);
|
||||
}
|
||||
|
||||
public static async Task<object> Show(IWindowService windowService, List<IPrerequisitesSubject> subjects, string cancelLabel = "Cancel")
|
||||
|
||||
@ -12,6 +12,7 @@ using Artemis.UI.Avalonia.Exceptions;
|
||||
using Artemis.UI.Avalonia.Ninject.Factories;
|
||||
using Artemis.UI.Avalonia.Shared;
|
||||
using Artemis.UI.Avalonia.Shared.Services.Interfaces;
|
||||
using Avalonia.Threading;
|
||||
using Ninject;
|
||||
using ReactiveUI;
|
||||
|
||||
@ -53,9 +54,13 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
|
||||
_pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginToggled;
|
||||
|
||||
OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(x => x.IsEnabled).Select(isEnabled => isEnabled && Plugin.ConfigurationDialog != null));
|
||||
InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites));
|
||||
RemovePrerequisites = ReactiveCommand.CreateFromTask<bool>(ExecuteRemovePrerequisites, this.WhenAnyValue(x => x.CanRemovePrerequisites));
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> OpenSettings { get; }
|
||||
public ReactiveCommand<Unit, Unit> InstallPrerequisites { get; }
|
||||
public ReactiveCommand<bool, Unit> RemovePrerequisites { get; }
|
||||
|
||||
public ObservableCollection<PluginFeatureViewModel> PluginFeatures { get; }
|
||||
|
||||
@ -137,7 +142,8 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
|
||||
{
|
||||
bool wasEnabled = IsEnabled;
|
||||
|
||||
_pluginManagementService.UnloadPlugin(Plugin);
|
||||
await Task.Run(() => _pluginManagementService.UnloadPlugin(Plugin));
|
||||
|
||||
PluginFeatures.Clear();
|
||||
|
||||
Plugin = _pluginManagementService.LoadPlugin(Plugin.Directory);
|
||||
@ -150,7 +156,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
|
||||
_notificationService.CreateNotification().WithTitle("Reloaded plugin.").Show();
|
||||
}
|
||||
|
||||
public async Task InstallPrerequisites()
|
||||
public async Task ExecuteInstallPrerequisites()
|
||||
{
|
||||
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
|
||||
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled));
|
||||
@ -159,7 +165,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
|
||||
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
|
||||
}
|
||||
|
||||
public async Task RemovePrerequisites(bool forPluginRemoval = false)
|
||||
public async Task ExecuteRemovePrerequisites(bool forPluginRemoval = false)
|
||||
{
|
||||
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
|
||||
subjects.AddRange(!forPluginRemoval ? Plugin.Features.Where(f => f.AlwaysEnabled) : Plugin.Features);
|
||||
@ -200,7 +206,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
|
||||
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
|
||||
subjects.AddRange(Plugin.Features);
|
||||
if (subjects.Any(s => s.Prerequisites.Any(p => p.UninstallActions.Any())))
|
||||
await RemovePrerequisites(true);
|
||||
await ExecuteRemovePrerequisites(true);
|
||||
|
||||
try
|
||||
{
|
||||
@ -284,7 +290,7 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Run(() =>
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -292,10 +298,10 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.ViewModels
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_notificationService.CreateNotification()
|
||||
await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification()
|
||||
.WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}")
|
||||
.HavingButton(b => b.WithText("View logs").WithAction(ShowLogsFolder))
|
||||
.Show();
|
||||
.Show());
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@ -27,12 +27,10 @@
|
||||
Icon="{Binding FeatureInfo.ResolvedIcon}"
|
||||
Width="20"
|
||||
Height="20"
|
||||
|
||||
IsVisible="{Binding LoadException, Converter={x:Static ObjectConverters.IsNull}}" />
|
||||
|
||||
<Button Grid.Column="0"
|
||||
Margin="-8"
|
||||
|
||||
Classes="AppBarButton icon-button"
|
||||
IsVisible="{Binding LoadException, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
Foreground="#E74C4C"
|
||||
ToolTip.Tip="An exception occurred while enabling this feature, click to view"
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using Artemis.UI.Avalonia.Screens.Plugins.ViewModels;
|
||||
using Avalonia;
|
||||
using Avalonia.Markup.Xaml;
|
||||
@ -19,22 +20,16 @@ namespace Artemis.UI.Avalonia.Screens.Plugins.Views
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
ViewModel!.ConfigurationViewModel.CloseRequested += ConfigurationViewModelOnCloseRequested;
|
||||
Disposable.Create(HandleDeactivation).DisposeWith(disposables);
|
||||
Observable.FromEventPattern(
|
||||
x => ViewModel!.ConfigurationViewModel.CloseRequested += x,
|
||||
x => ViewModel!.ConfigurationViewModel.CloseRequested -= x
|
||||
)
|
||||
.Subscribe(_ => Close())
|
||||
.DisposeWith(disposables);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void HandleDeactivation()
|
||||
{
|
||||
ViewModel!.ConfigurationViewModel.CloseRequested -= ConfigurationViewModelOnCloseRequested;
|
||||
}
|
||||
|
||||
private void ConfigurationViewModelOnCloseRequested(object? sender, EventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user