diff --git a/.github/workflows/docfx.yml b/.github/workflows/docfx.yml index 50416a113..044774480 100644 --- a/.github/workflows/docfx.yml +++ b/.github/workflows/docfx.yml @@ -1,6 +1,6 @@ name: Master - DocFX -on: +on: workflow_dispatch: push: branches: @@ -16,7 +16,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v2 with: - dotnet-version: '6.0.x' + dotnet-version: "7.0.x" - name: Setup DocFX run: choco install docfx -y - name: Build Core @@ -32,4 +32,4 @@ jobs: username: ${{ secrets.FTP_USER }} password: ${{ secrets.FTP_PASSWORD }} local-dir: docfx/docfx_project/_site/ - server-dir: /httpdocs/docs/ \ No newline at end of file + server-dir: /httpdocs/docs/ diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 36d37e0d3..96762a278 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -1,8 +1,10 @@ name: Master - Build -on: +on: workflow_dispatch: push: + branches: + - master jobs: version: @@ -26,11 +28,11 @@ jobs: # If we're not in master, add the branch name to the version so it counts as prerelease if ($BranchName -ne "master") { $VersionNumber += "-$BranchName" } "version-number=$VersionNumber" >> $Env:GITHUB_OUTPUT - + build: needs: version strategy: - matrix: + matrix: include: - os: windows-latest rid: win10-x64 diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index c45941a79..05daa25f3 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -1,8 +1,10 @@ name: Publish Nuget packages -on: +on: workflow_dispatch: push: + branches: + - master jobs: version: @@ -43,4 +45,4 @@ jobs: - name: Pack Artemis.UI.Shared run: dotnet pack -c Release -p:Version=${{ needs.version.outputs.version-number }} src/Artemis.UI.Shared/Artemis.UI.Shared.csproj - name: Push Nugets - run: dotnet nuget push **/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate \ No newline at end of file + run: dotnet nuget push **/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate diff --git a/src/Artemis.Core/Exceptions/ArtemisPluginException.cs b/src/Artemis.Core/Exceptions/ArtemisPluginException.cs index 53f9fd30f..547ce8ead 100644 --- a/src/Artemis.Core/Exceptions/ArtemisPluginException.cs +++ b/src/Artemis.Core/Exceptions/ArtemisPluginException.cs @@ -44,31 +44,9 @@ public class ArtemisPluginException : Exception public ArtemisPluginException(string message, Exception inner) : base(message, inner) { } - - /// - /// Creates a new instance of the class - /// - public ArtemisPluginException(string message, string helpDocument) : base(message) - { - HelpDocument = helpDocument; - } - - /// - /// Creates a new instance of the class - /// - public ArtemisPluginException(string message, Exception inner, string helpDocument) : base(message, inner) - { - HelpDocument = helpDocument; - } /// /// Gets the plugin the error is related to /// public Plugin? Plugin { get; } - - /// - /// Gets or sets the help document related to this exception. - /// - public string? HelpDocument { get; } - } \ No newline at end of file diff --git a/src/Artemis.Core/Models/BreakableModel.cs b/src/Artemis.Core/Models/BreakableModel.cs index 1910cc2cc..f0338cd0f 100644 --- a/src/Artemis.Core/Models/BreakableModel.cs +++ b/src/Artemis.Core/Models/BreakableModel.cs @@ -59,7 +59,7 @@ public abstract class BreakableModel : CorePropertyChanged, IBreakableModel } /// - public void SetBrokenState(string state, Exception? exception) + public void SetBrokenState(string state, Exception? exception = null) { BrokenState = state ?? throw new ArgumentNullException(nameof(state)); BrokenStateException = exception; diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index a92aabc35..85683eb49 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -16,6 +16,9 @@ namespace Artemis.Core; /// public sealed class Layer : RenderProfileElement { + private const string BROKEN_STATE_BRUSH_NOT_FOUND = "Failed to load layer brush, ensure the plugin is enabled"; + private const string BROKEN_STATE_INIT_FAILED = "Failed to initialize layer brush"; + private readonly List _renderCopies = new(); private LayerGeneralProperties _general = new(); private LayerTransformProperties _transform = new(); @@ -261,7 +264,10 @@ public sealed class Layer : RenderProfileElement private void LayerBrushStoreOnLayerBrushRemoved(object? sender, LayerBrushStoreEvent e) { if (LayerBrush?.Descriptor == e.Registration.LayerBrushDescriptor) + { DeactivateLayerBrush(); + SetBrokenState(BROKEN_STATE_BRUSH_NOT_FOUND); + } } private void LayerBrushStoreOnLayerBrushAdded(object? sender, LayerBrushStoreEvent e) @@ -807,33 +813,46 @@ public sealed class Layer : RenderProfileElement /// /// Changes the current layer brush to the provided layer brush and activates it /// - public void ChangeLayerBrush(BaseLayerBrush? layerBrush) + public void ChangeLayerBrush(BaseLayerBrush layerBrush) { - BaseLayerBrush? oldLayerBrush = LayerBrush; + if (layerBrush == null) + throw new ArgumentNullException(nameof(layerBrush)); - General.BrushReference.SetCurrentValue(layerBrush != null ? new LayerBrushReference(layerBrush.Descriptor) : null); + BaseLayerBrush? oldLayerBrush = LayerBrush; + General.BrushReference.SetCurrentValue(new LayerBrushReference(layerBrush.Descriptor)); LayerBrush = layerBrush; + // Don't dispose the brush, only disable it + // That way an undo-action does not need to worry about disposed brushes oldLayerBrush?.InternalDisable(); - - if (LayerBrush != null) - ActivateLayerBrush(); - else - OnLayerBrushUpdated(); + ActivateLayerBrush(); } - internal void ActivateLayerBrush() + private void ActivateLayerBrush() { try { + // If the brush is null, try to instantiate it if (LayerBrush == null) { - // If the brush is null, try to instantiate it + // Ensure the provider is loaded and still provides the expected brush LayerBrushReference? brushReference = General.BrushReference.CurrentValue; if (brushReference?.LayerBrushProviderId != null && brushReference.BrushType != null) - ChangeLayerBrush(LayerBrushStore.Get(brushReference.LayerBrushProviderId, brushReference.BrushType)?.LayerBrushDescriptor.CreateInstance(this, LayerEntity.LayerBrush)); - // If that's not possible there's nothing to do - return; + { + // Only apply the brush if it is successfully retrieved from the store and instantiated + BaseLayerBrush? layerBrush = LayerBrushStore.Get(brushReference.LayerBrushProviderId, brushReference.BrushType)?.LayerBrushDescriptor.CreateInstance(this, LayerEntity.LayerBrush); + if (layerBrush != null) + { + ClearBrokenState(BROKEN_STATE_BRUSH_NOT_FOUND); + ChangeLayerBrush(layerBrush); + } + // If that's not possible there's nothing to do + else + { + SetBrokenState(BROKEN_STATE_BRUSH_NOT_FOUND); + return; + } + } } General.ShapeType.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation; @@ -846,15 +865,15 @@ public sealed class Layer : RenderProfileElement } OnLayerBrushUpdated(); - ClearBrokenState("Failed to initialize layer brush"); + ClearBrokenState(BROKEN_STATE_INIT_FAILED); } catch (Exception e) { - SetBrokenState("Failed to initialize layer brush", e); + SetBrokenState(BROKEN_STATE_INIT_FAILED, e); } } - internal void DeactivateLayerBrush() + private void DeactivateLayerBrush() { if (LayerBrush == null) return; diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index 45b469146..ff90181bc 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -57,7 +57,7 @@ public class Plugin : CorePropertyChanged, IDisposable /// Gets or sets a configuration dialog for this plugin that is accessible in the UI under Settings > Plugins /// public IPluginConfigurationDialog? ConfigurationDialog { get; set; } - + /// /// Indicates whether the user enabled the plugin or not /// diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs index 6421749f8..e8c3f5d50 100644 --- a/src/Artemis.Core/Plugins/PluginInfo.cs +++ b/src/Artemis.Core/Plugins/PluginInfo.cs @@ -27,6 +27,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject private bool _requiresAdmin; private string _version = null!; private Uri? _website; + private Uri? _helpPage; internal PluginInfo() { @@ -91,6 +92,16 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject get => _repository; set => SetAndNotify(ref _repository, value); } + + /// + /// Gets or sets the help page of this plugin + /// + [JsonProperty] + public Uri? HelpPage + { + get => _helpPage; + set => SetAndNotify(ref _helpPage, value); + } /// /// The plugins display icon that's shown in the settings see for diff --git a/src/Artemis.UI.Shared/Services/Builders/OpenFileDialogBuilder.cs b/src/Artemis.UI.Shared/Services/Builders/OpenFileDialogBuilder.cs index e3785eea9..616b7977a 100644 --- a/src/Artemis.UI.Shared/Services/Builders/OpenFileDialogBuilder.cs +++ b/src/Artemis.UI.Shared/Services/Builders/OpenFileDialogBuilder.cs @@ -78,6 +78,6 @@ public class OpenFileDialogBuilder public async Task ShowAsync() { IReadOnlyList files = await _parent.StorageProvider.OpenFilePickerAsync(_options); - return files.Select(f => f.Path.AbsolutePath).ToArray(); + return files.Select(f => f.Path.LocalPath).ToArray(); } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerBrush.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerBrush.cs index 1c9a508c2..ce92133d6 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerBrush.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerBrush.cs @@ -54,7 +54,8 @@ public class ChangeLayerBrush : IProfileEditorCommand, IDisposable /// public void Undo() { - _layer.ChangeLayerBrush(_previousBrush); + if (_previousBrush != null) + _layer.ChangeLayerBrush(_previousBrush); _executed = false; } diff --git a/src/Artemis.UI.Shared/Styles/Artemis.axaml b/src/Artemis.UI.Shared/Styles/Artemis.axaml index 9907a4b20..988213ad4 100644 --- a/src/Artemis.UI.Shared/Styles/Artemis.axaml +++ b/src/Artemis.UI.Shared/Styles/Artemis.axaml @@ -21,6 +21,7 @@ + diff --git a/src/Artemis.UI.Shared/Styles/BrokenState.axaml b/src/Artemis.UI.Shared/Styles/BrokenState.axaml new file mode 100644 index 000000000..65ef465c0 --- /dev/null +++ b/src/Artemis.UI.Shared/Styles/BrokenState.axaml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 4e6b4e383..a1252420a 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -52,5 +52,17 @@ UpdatingTabView.axaml Code + + PluginFeatureView.axaml + Code + + + PluginPrerequisiteActionView.axaml + Code + + + PluginPrerequisiteView.axaml + Code + \ No newline at end of file diff --git a/src/Artemis.UI/DryIoc/Factories/IVMFactory.cs b/src/Artemis.UI/DryIoc/Factories/IVMFactory.cs index 7d3e344aa..d410e3cfc 100644 --- a/src/Artemis.UI/DryIoc/Factories/IVMFactory.cs +++ b/src/Artemis.UI/DryIoc/Factories/IVMFactory.cs @@ -7,6 +7,8 @@ using Artemis.Core.LayerEffects; using Artemis.Core.ScriptingProviders; using Artemis.UI.Screens.Device; using Artemis.UI.Screens.Plugins; +using Artemis.UI.Screens.Plugins.Features; +using Artemis.UI.Screens.Plugins.Prerequisites; using Artemis.UI.Screens.ProfileEditor; using Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes; using Artemis.UI.Screens.ProfileEditor.ProfileTree; diff --git a/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesInstallDialogView.axaml b/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesInstallDialogView.axaml index 305c68324..9ac570871 100644 --- a/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesInstallDialogView.axaml +++ b/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesInstallDialogView.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" + xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisitesInstallDialogView" x:DataType="plugins:PluginPrerequisitesInstallDialogViewModel"> @@ -34,7 +35,7 @@ SelectedItem="{CompiledBinding ActivePrerequisite, Mode=OneWay}" IsHitTestVisible="False"> - + diff --git a/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesInstallDialogViewModel.cs b/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesInstallDialogViewModel.cs index a8de70fbd..649698e0d 100644 --- a/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesInstallDialogViewModel.cs +++ b/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesInstallDialogViewModel.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Artemis.Core; using Artemis.UI.DryIoc.Factories; +using Artemis.UI.Screens.Plugins.Prerequisites; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using Avalonia.Threading; diff --git a/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesUninstallDialogView.axaml b/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesUninstallDialogView.axaml index d2c15c7fd..0acb85de7 100644 --- a/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesUninstallDialogView.axaml +++ b/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesUninstallDialogView.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" + xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisitesUninstallDialogView" x:DataType="plugins:PluginPrerequisitesUninstallDialogViewModel"> @@ -34,7 +35,7 @@ SelectedItem="{CompiledBinding ActivePrerequisite, Mode=OneWay}" IsHitTestVisible="False"> - + diff --git a/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesUninstallDialogViewModel.cs b/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesUninstallDialogViewModel.cs index b65e72d23..b443de9b1 100644 --- a/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesUninstallDialogViewModel.cs +++ b/src/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesUninstallDialogViewModel.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.DryIoc.Factories; +using Artemis.UI.Screens.Plugins.Prerequisites; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using Avalonia.Threading; diff --git a/src/Artemis.UI/Screens/Plugins/PluginFeatureView.axaml b/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureView.axaml similarity index 95% rename from src/Artemis.UI/Screens/Plugins/PluginFeatureView.axaml rename to src/Artemis.UI/Screens/Plugins/Features/PluginFeatureView.axaml index 790c38f82..0b8bd3873 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginFeatureView.axaml +++ b/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureView.axaml @@ -5,9 +5,10 @@ xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins" + xmlns:features="clr-namespace:Artemis.UI.Screens.Plugins.Features" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Screens.Plugins.PluginFeatureView" - x:DataType="plugins:PluginFeatureViewModel"> + x:Class="Artemis.UI.Screens.Plugins.Features.PluginFeatureView" + x:DataType="features:PluginFeatureViewModel"> diff --git a/src/Artemis.UI/Screens/Plugins/PluginFeatureView.axaml.cs b/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureView.axaml.cs similarity index 74% rename from src/Artemis.UI/Screens/Plugins/PluginFeatureView.axaml.cs rename to src/Artemis.UI/Screens/Plugins/Features/PluginFeatureView.axaml.cs index 7f21c9ce0..1beddfbbf 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginFeatureView.axaml.cs +++ b/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureView.axaml.cs @@ -1,7 +1,6 @@ -using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; -namespace Artemis.UI.Screens.Plugins; +namespace Artemis.UI.Screens.Plugins.Features; public partial class PluginFeatureView : ReactiveUserControl { diff --git a/src/Artemis.UI/Screens/Plugins/PluginFeatureViewModel.cs b/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureViewModel.cs similarity index 90% rename from src/Artemis.UI/Screens/Plugins/PluginFeatureViewModel.cs rename to src/Artemis.UI/Screens/Plugins/Features/PluginFeatureViewModel.cs index 5c2abaea2..2d83d9e6c 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginFeatureViewModel.cs +++ b/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureViewModel.cs @@ -18,12 +18,11 @@ using Avalonia.Threading; using Material.Icons; using ReactiveUI; -namespace Artemis.UI.Screens.Plugins; +namespace Artemis.UI.Screens.Plugins.Features; public class PluginFeatureViewModel : ActivatableViewModelBase { private readonly ICoreService _coreService; - private readonly INotificationService _notificationService; private readonly IPluginManagementService _pluginManagementService; private readonly IWindowService _windowService; private bool _enabling; @@ -32,12 +31,10 @@ public class PluginFeatureViewModel : ActivatableViewModelBase bool showShield, ICoreService coreService, IWindowService windowService, - INotificationService notificationService, IPluginManagementService pluginManagementService) { _coreService = coreService; _windowService = windowService; - _notificationService = notificationService; _pluginManagementService = pluginManagementService; FeatureInfo = pluginFeatureInfo; @@ -176,11 +173,7 @@ public class PluginFeatureViewModel : ActivatableViewModelBase if (FeatureInfo.Instance == null) { this.RaisePropertyChanged(nameof(IsEnabled)); - _notificationService.CreateNotification() - .WithMessage($"Feature '{FeatureInfo.Name}' is in a broken state and cannot enable.") - .HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder)) - .WithSeverity(NotificationSeverity.Error) - .Show(); + await ShowFailureDialog($"Failed to enable '{FeatureInfo.Name}'", $"Feature '{FeatureInfo.Name}' is in a broken state and cannot enable."); return; } @@ -215,11 +208,7 @@ public class PluginFeatureViewModel : ActivatableViewModelBase } catch (Exception e) { - _notificationService.CreateNotification() - .WithMessage($"Failed to enable '{FeatureInfo.Name}'.\r\n{e.Message}") - .HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder)) - .WithSeverity(NotificationSeverity.Error) - .Show(); + await ShowFailureDialog($"Failed to enable '{FeatureInfo.Name}'", e.Message); } finally { @@ -254,4 +243,17 @@ public class PluginFeatureViewModel : ActivatableViewModelBase { this.RaisePropertyChanged(nameof(CanToggleEnabled)); } + + private async Task ShowFailureDialog(string action, string message) + { + ContentDialogBuilder builder = _windowService.CreateContentDialog() + .WithTitle(action) + .WithContent(message) + .HavingPrimaryButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder)); + // If available, add a secondary button pointing to the support page + if (FeatureInfo.Plugin.Info.HelpPage != null) + builder = builder.HavingSecondaryButton(b => b.WithText("Open support page").WithAction(() => Utilities.OpenUrl(FeatureInfo.Plugin.Info.HelpPage.ToString()))); + + await builder.ShowAsync(); + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/PluginSettingsViewModel.cs b/src/Artemis.UI/Screens/Plugins/PluginSettingsViewModel.cs index 9d3e64329..81d287b42 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Plugins/PluginSettingsViewModel.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.DryIoc.Factories; +using Artemis.UI.Screens.Plugins.Features; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using ReactiveUI; diff --git a/src/Artemis.UI/Screens/Plugins/PluginSettingsWindowView.axaml b/src/Artemis.UI/Screens/Plugins/PluginSettingsWindowView.axaml index 12dd5b763..a38d666b6 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginSettingsWindowView.axaml +++ b/src/Artemis.UI/Screens/Plugins/PluginSettingsWindowView.axaml @@ -3,10 +3,12 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia" + xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Plugins.PluginSettingsWindowView" + x:DataType="plugins:PluginSettingsWindowViewModel" Icon="/Assets/Images/Logo/application.ico" - Title="{Binding DisplayName}" + Title="{CompiledBinding DisplayName}" Width="800" Height="800" WindowStartupLocation="CenterOwner"> diff --git a/src/Artemis.UI/Screens/Plugins/PluginView.axaml b/src/Artemis.UI/Screens/Plugins/PluginView.axaml index 27c47a631..640279366 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginView.axaml +++ b/src/Artemis.UI/Screens/Plugins/PluginView.axaml @@ -46,9 +46,9 @@ - - - + + + @@ -70,6 +70,7 @@ + @@ -81,19 +82,31 @@ - - - + + + + + + + + + + NavigateUri="{CompiledBinding Plugin.Info.Website}" + ToolTip.Tip="{CompiledBinding Plugin.Info.Website}"> + NavigateUri="{CompiledBinding Plugin.Info.Repository}" + ToolTip.Tip="{CompiledBinding Plugin.Info.Repository}"> diff --git a/src/Artemis.UI/Screens/Plugins/PluginViewModel.cs b/src/Artemis.UI/Screens/Plugins/PluginViewModel.cs index 35d37aa0e..96ec6e772 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginViewModel.cs +++ b/src/Artemis.UI/Screens/Plugins/PluginViewModel.cs @@ -28,7 +28,7 @@ public class PluginViewModel : ActivatableViewModelBase private bool _canRemovePrerequisites; private bool _enabling; private Plugin _plugin; - private Window? _window; + private Window? _settingsWindow; public PluginViewModel(Plugin plugin, ReactiveCommand? reload, @@ -72,7 +72,7 @@ public class PluginViewModel : ActivatableViewModelBase { Plugin.Enabled -= OnPluginToggled; Plugin.Disabled -= OnPluginToggled; - _window?.Close(); + _settingsWindow?.Close(); }).DisposeWith(d); }); } @@ -128,11 +128,7 @@ public class PluginViewModel : ActivatableViewModelBase } catch (Exception e) { - await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification() - .WithSeverity(NotificationSeverity.Error) - .WithMessage($"Failed to disable plugin {Plugin.Info.Name}\r\n{e.Message}") - .HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder)) - .Show()); + await ShowUpdateEnableFailure(enable, e); } finally { @@ -167,11 +163,7 @@ public class PluginViewModel : ActivatableViewModelBase } catch (Exception e) { - await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification() - .WithSeverity(NotificationSeverity.Error) - .WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}") - .HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder)) - .Show()); + await ShowUpdateEnableFailure(enable, e); } finally { @@ -180,7 +172,6 @@ public class PluginViewModel : ActivatableViewModelBase } } - public void CheckPrerequisites() { CanInstallPrerequisites = false; @@ -199,10 +190,10 @@ public class PluginViewModel : ActivatableViewModelBase if (Plugin.ConfigurationDialog == null) return; - if (_window != null) + if (_settingsWindow != null) { - _window.WindowState = WindowState.Normal; - _window.Activate(); + _settingsWindow.WindowState = WindowState.Normal; + _settingsWindow.Activate(); return; } @@ -211,8 +202,8 @@ public class PluginViewModel : ActivatableViewModelBase if (Plugin.Resolve(Plugin.ConfigurationDialog.Type) is not PluginConfigurationViewModel viewModel) throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}"); - _window = _windowService.ShowWindow(new PluginSettingsWindowViewModel(viewModel)); - _window.Closed += (_, _) => _window = null; + _settingsWindow = _windowService.ShowWindow(new PluginSettingsWindowViewModel(viewModel)); + _settingsWindow.Closed += (_, _) => _settingsWindow = null; } catch (Exception e) { @@ -306,6 +297,20 @@ public class PluginViewModel : ActivatableViewModelBase _windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e); } } + + private async Task ShowUpdateEnableFailure(bool enable, Exception e) + { + string action = enable ? "enable" : "disable"; + ContentDialogBuilder builder = _windowService.CreateContentDialog() + .WithTitle($"Failed to {action} plugin {Plugin.Info.Name}") + .WithContent(e.Message) + .HavingPrimaryButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder)); + // If available, add a secondary button pointing to the support page + if (Plugin.Info.HelpPage != null) + builder = builder.HavingSecondaryButton(b => b.WithText("Open support page").WithAction(() => Utilities.OpenUrl(Plugin.Info.HelpPage.ToString()))); + + await builder.ShowAsync(); + } private void OnPluginToggled(object? sender, EventArgs e) { @@ -313,7 +318,7 @@ public class PluginViewModel : ActivatableViewModelBase { this.RaisePropertyChanged(nameof(IsEnabled)); if (!IsEnabled) - _window?.Close(); + _settingsWindow?.Close(); }); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteActionView.axaml b/src/Artemis.UI/Screens/Plugins/Prerequisites/PluginPrerequisiteActionView.axaml similarity index 82% rename from src/Artemis.UI/Screens/Plugins/PluginPrerequisiteActionView.axaml rename to src/Artemis.UI/Screens/Plugins/Prerequisites/PluginPrerequisiteActionView.axaml index 0cc1745d7..d7a09a975 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteActionView.axaml +++ b/src/Artemis.UI/Screens/Plugins/Prerequisites/PluginPrerequisiteActionView.axaml @@ -3,9 +3,10 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins" + xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisiteActionView" - x:DataType="plugins:PluginPrerequisiteActionViewModel"> + x:Class="Artemis.UI.Screens.Plugins.Prerequisites.PluginPrerequisiteActionView" + x:DataType="prerequisites:PluginPrerequisiteActionViewModel"> + x:Class="Artemis.UI.Screens.Plugins.Prerequisites.PluginPrerequisiteView" + x:DataType="prerequisites:PluginPrerequisiteViewModel"> diff --git a/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteView.axaml.cs b/src/Artemis.UI/Screens/Plugins/Prerequisites/PluginPrerequisiteView.axaml.cs similarity index 65% rename from src/Artemis.UI/Screens/Plugins/PluginPrerequisiteView.axaml.cs rename to src/Artemis.UI/Screens/Plugins/Prerequisites/PluginPrerequisiteView.axaml.cs index 50cbd2c39..043fbce7b 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteView.axaml.cs +++ b/src/Artemis.UI/Screens/Plugins/Prerequisites/PluginPrerequisiteView.axaml.cs @@ -1,7 +1,6 @@ -using Avalonia.Markup.Xaml; -using Avalonia.ReactiveUI; +using Avalonia.ReactiveUI; -namespace Artemis.UI.Screens.Plugins; +namespace Artemis.UI.Screens.Plugins.Prerequisites; public partial class PluginPrerequisiteView : ReactiveUserControl { diff --git a/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs b/src/Artemis.UI/Screens/Plugins/Prerequisites/PluginPrerequisiteViewModel.cs similarity index 98% rename from src/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs rename to src/Artemis.UI/Screens/Plugins/Prerequisites/PluginPrerequisiteViewModel.cs index a9ed4a8b4..58cce5d2e 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs +++ b/src/Artemis.UI/Screens/Plugins/Prerequisites/PluginPrerequisiteViewModel.cs @@ -8,7 +8,7 @@ using Artemis.Core; using Artemis.UI.Shared; using ReactiveUI; -namespace Artemis.UI.Screens.Plugins; +namespace Artemis.UI.Screens.Plugins.Prerequisites; public class PluginPrerequisiteViewModel : ActivatableViewModelBase { diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemView.axaml index b9301627e..60f0456da 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/FolderTreeItemView.axaml @@ -4,21 +4,19 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:profileTree="clr-namespace:Artemis.UI.Screens.ProfileEditor.ProfileTree" + xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.FolderTreeItemView" x:DataType="profileTree:FolderTreeItemViewModel"> - + - + - - - + diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeView.axaml b/src/Artemis.UI/Screens/VisualScripting/NodeView.axaml index 7adfdbbdf..62838e70c 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeView.axaml +++ b/src/Artemis.UI/Screens/VisualScripting/NodeView.axaml @@ -39,21 +39,16 @@ Background="{DynamicResource ContentDialogBackground}"> - + + + - + + + + + + + + + + + + + + + \ No newline at end of file