From b59e898dd3f392cda34c98a7059dec2d1b0fa2b0 Mon Sep 17 00:00:00 2001 From: Robert Date: Thu, 13 Apr 2023 17:41:54 +0200 Subject: [PATCH 1/3] Plugins - Separated VMs into folders Plugins - Added help pages --- .../Exceptions/ArtemisPluginException.cs | 12 ++--- src/Artemis.Core/Plugins/IPluginHelpPage.cs | 22 +++++++++ .../Plugins/MarkdownPluginHelpPage.cs | 41 ++++++++++++++++ src/Artemis.Core/Plugins/Plugin.cs | 3 ++ src/Artemis.UI/Artemis.UI.csproj | 20 ++++++++ src/Artemis.UI/DryIoc/Factories/IVMFactory.cs | 2 + ...PluginPrerequisitesInstallDialogView.axaml | 3 +- ...uginPrerequisitesInstallDialogViewModel.cs | 1 + ...uginPrerequisitesUninstallDialogView.axaml | 3 +- ...inPrerequisitesUninstallDialogViewModel.cs | 1 + .../{ => Features}/PluginFeatureView.axaml | 5 +- .../{ => Features}/PluginFeatureView.axaml.cs | 3 +- .../{ => Features}/PluginFeatureViewModel.cs | 2 +- .../Help/MarkdownPluginHelpPageView.axaml | 27 +++++++++++ .../Help/MarkdownPluginHelpPageView.axaml.cs | 17 +++++++ .../Help/MarkdownPluginHelpPageViewModel.cs | 40 ++++++++++++++++ .../Plugins/Help/PluginHelpWindowView.axaml | 31 ++++++++++++ .../Help/PluginHelpWindowView.axaml.cs | 21 +++++++++ .../Plugins/Help/PluginHelpWindowViewModel.cs | 32 +++++++++++++ .../Plugins/PluginSettingsViewModel.cs | 1 + .../Plugins/PluginSettingsWindowView.axaml | 4 +- .../Screens/Plugins/PluginView.axaml | 33 +++++++++---- .../Screens/Plugins/PluginViewModel.cs | 47 +++++++++++++++---- .../PluginPrerequisiteActionView.axaml | 5 +- .../PluginPrerequisiteActionView.axaml.cs | 3 +- .../PluginPrerequisiteActionViewModel.cs | 2 +- .../PluginPrerequisiteView.axaml | 5 +- .../PluginPrerequisiteView.axaml.cs | 5 +- .../PluginPrerequisiteViewModel.cs | 2 +- 29 files changed, 350 insertions(+), 43 deletions(-) create mode 100644 src/Artemis.Core/Plugins/IPluginHelpPage.cs create mode 100644 src/Artemis.Core/Plugins/MarkdownPluginHelpPage.cs rename src/Artemis.UI/Screens/Plugins/{ => Features}/PluginFeatureView.axaml (95%) rename src/Artemis.UI/Screens/Plugins/{ => Features}/PluginFeatureView.axaml.cs (74%) rename src/Artemis.UI/Screens/Plugins/{ => Features}/PluginFeatureViewModel.cs (99%) create mode 100644 src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml create mode 100644 src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs create mode 100644 src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml create mode 100644 src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowViewModel.cs rename src/Artemis.UI/Screens/Plugins/{ => Prerequisites}/PluginPrerequisiteActionView.axaml (82%) rename src/Artemis.UI/Screens/Plugins/{ => Prerequisites}/PluginPrerequisiteActionView.axaml.cs (73%) rename src/Artemis.UI/Screens/Plugins/{ => Prerequisites}/PluginPrerequisiteActionViewModel.cs (84%) rename src/Artemis.UI/Screens/Plugins/{ => Prerequisites}/PluginPrerequisiteView.axaml (87%) rename src/Artemis.UI/Screens/Plugins/{ => Prerequisites}/PluginPrerequisiteView.axaml.cs (65%) rename src/Artemis.UI/Screens/Plugins/{ => Prerequisites}/PluginPrerequisiteViewModel.cs (98%) diff --git a/src/Artemis.Core/Exceptions/ArtemisPluginException.cs b/src/Artemis.Core/Exceptions/ArtemisPluginException.cs index 53f9fd30f..6473f0293 100644 --- a/src/Artemis.Core/Exceptions/ArtemisPluginException.cs +++ b/src/Artemis.Core/Exceptions/ArtemisPluginException.cs @@ -48,17 +48,17 @@ public class ArtemisPluginException : Exception /// /// Creates a new instance of the class /// - public ArtemisPluginException(string message, string helpDocument) : base(message) + public ArtemisPluginException(string message, string helpPageId) : base(message) { - HelpDocument = helpDocument; + HelpPageId = helpPageId; } /// /// Creates a new instance of the class /// - public ArtemisPluginException(string message, Exception inner, string helpDocument) : base(message, inner) + public ArtemisPluginException(string message, Exception inner, string helpPageId) : base(message, inner) { - HelpDocument = helpDocument; + HelpPageId = helpPageId; } /// @@ -67,8 +67,8 @@ public class ArtemisPluginException : Exception public Plugin? Plugin { get; } /// - /// Gets or sets the help document related to this exception. + /// Gets the ID of the help page to display for this exception. /// - public string? HelpDocument { get; } + public string? HelpPageId { get; } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/IPluginHelpPage.cs b/src/Artemis.Core/Plugins/IPluginHelpPage.cs new file mode 100644 index 000000000..1375d6716 --- /dev/null +++ b/src/Artemis.Core/Plugins/IPluginHelpPage.cs @@ -0,0 +1,22 @@ +namespace Artemis.Core; + +/// +/// Represents a plugin related help page +/// +public interface IPluginHelpPage +{ + /// + /// Gets the plugin the help page belongs to. + /// + Plugin Plugin { get; } + + /// + /// Gets the title of the help page. + /// + public string Title { get; } + + /// + /// An ID used to quickly lead users to the help page in case of an error. + /// + public string Id { get; } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/MarkdownPluginHelpPage.cs b/src/Artemis.Core/Plugins/MarkdownPluginHelpPage.cs new file mode 100644 index 000000000..09554fa29 --- /dev/null +++ b/src/Artemis.Core/Plugins/MarkdownPluginHelpPage.cs @@ -0,0 +1,41 @@ +using System.IO; + +namespace Artemis.Core; + +/// +/// Represents a plugin related help page +/// +public class MarkdownPluginHelpPage : IPluginHelpPage +{ + /// + /// Creates a new instance of the class. + /// + /// The plugin to display the markdown for. + /// A file path relative to the plugin or absolute, pointing to the markdown to display + /// The ID of the help page, used to quickly lead users to it in case of errors. + /// + public MarkdownPluginHelpPage(Plugin plugin, string title, string id, string markdownFile) + { + Plugin = plugin; + Title = title; + Id = id; + MarkdownFile = Path.IsPathRooted(markdownFile) ? markdownFile : Plugin.ResolveRelativePath(markdownFile); + + if (!File.Exists(MarkdownFile)) + throw new FileNotFoundException($"Could not find markdown file at \"{MarkdownFile}\""); + } + + /// + public Plugin Plugin { get; } + + /// + public string Title { get; } + + /// + public string Id { get; } + + /// + /// Gets the absolute path to the markdown that is to be displayed. + /// + public string MarkdownFile { get; } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index 45b469146..290db1224 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -36,6 +36,7 @@ public class Plugin : CorePropertyChanged, IDisposable Features = new ReadOnlyCollection(_features); Profilers = new ReadOnlyCollection(_profilers); + HelpPages = new List(); } /// @@ -58,6 +59,8 @@ public class Plugin : CorePropertyChanged, IDisposable /// public IPluginConfigurationDialog? ConfigurationDialog { get; set; } + public List HelpPages { get; } + /// /// Indicates whether the user enabled the plugin or not /// diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 4e6b4e383..ea9845315 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -52,5 +52,25 @@ UpdatingTabView.axaml Code + + MarkdownPluginHelpPageView.axaml + Code + + + PluginHelpWindowView.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 99% rename from src/Artemis.UI/Screens/Plugins/PluginFeatureViewModel.cs rename to src/Artemis.UI/Screens/Plugins/Features/PluginFeatureViewModel.cs index 5c2abaea2..2df1c64a9 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginFeatureViewModel.cs +++ b/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureViewModel.cs @@ -18,7 +18,7 @@ using Avalonia.Threading; using Material.Icons; using ReactiveUI; -namespace Artemis.UI.Screens.Plugins; +namespace Artemis.UI.Screens.Plugins.Features; public class PluginFeatureViewModel : ActivatableViewModelBase { diff --git a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml new file mode 100644 index 000000000..f090b7f6f --- /dev/null +++ b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs new file mode 100644 index 000000000..dde34b97d --- /dev/null +++ b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Artemis.UI.Screens.Plugins.Help; + +public partial class MarkdownPluginHelpPageView : UserControl +{ + public MarkdownPluginHelpPageView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs new file mode 100644 index 000000000..9f647b405 --- /dev/null +++ b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs @@ -0,0 +1,40 @@ +using System.IO; +using System.Reactive.Disposables; +using System.Threading.Tasks; +using Artemis.Core; +using Artemis.UI.Shared; +using ReactiveUI; + +namespace Artemis.UI.Screens.Plugins.Help; + +public class MarkdownPluginHelpPageViewModel : ActivatableViewModelBase, IPluginHelpPage +{ + private string? _markdownText; + private readonly MarkdownPluginHelpPage _helpPage; + + public MarkdownPluginHelpPageViewModel(MarkdownPluginHelpPage helpPage) + { + _helpPage = helpPage; + this.WhenActivated(d => Load().DisposeWith(d)); + } + + public string? MarkdownText + { + get => _markdownText; + set => RaiseAndSetIfChanged(ref _markdownText, value); + } + + /// + public Plugin Plugin => _helpPage.Plugin; + + /// + public string Title => _helpPage.Title; + + /// + public string Id => _helpPage.Id; + + private async Task Load() + { + MarkdownText ??= await File.ReadAllTextAsync(_helpPage.MarkdownFile); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml new file mode 100644 index 000000000..49b9551ac --- /dev/null +++ b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs new file mode 100644 index 000000000..0ccc2a611 --- /dev/null +++ b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs @@ -0,0 +1,21 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Artemis.UI.Screens.Plugins.Help; + +public partial class PluginHelpWindowView : Window +{ + public PluginHelpWindowView() + { + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowViewModel.cs b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowViewModel.cs new file mode 100644 index 000000000..b9ad2b37c --- /dev/null +++ b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowViewModel.cs @@ -0,0 +1,32 @@ +using System.Collections.ObjectModel; +using System.Linq; +using Artemis.Core; +using Artemis.UI.Shared; + +namespace Artemis.UI.Screens.Plugins.Help; + +public class PluginHelpWindowViewModel : ActivatableViewModelBase +{ + private IPluginHelpPage? _selectedHelpPage; + + public PluginHelpWindowViewModel(Plugin plugin, string? preselectId) + { + Plugin = plugin; + DisplayName = $"{Plugin.Info.Name} | Help"; + + // Populate help pages by wrapping MarkdownHelpPages into a MarkdownHelpPageViewModel + // other types are used directly, up to them to implement a VM directly as well + HelpPages = new ReadOnlyCollection(plugin.HelpPages.Select(p => p is MarkdownPluginHelpPage m ? new MarkdownPluginHelpPageViewModel(m) : p).ToList()); + + _selectedHelpPage = preselectId != null ? HelpPages.FirstOrDefault(p => p.Id == preselectId) : HelpPages.FirstOrDefault(); + } + + public Plugin Plugin { get; } + public ReadOnlyCollection HelpPages { get; } + + public IPluginHelpPage? SelectedHelpPage + { + get => _selectedHelpPage; + set => RaiseAndSetIfChanged(ref _selectedHelpPage, value); + } +} \ 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..cae9c18ff 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..7a52e391c 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginViewModel.cs +++ b/src/Artemis.UI/Screens/Plugins/PluginViewModel.cs @@ -4,10 +4,12 @@ using System.Collections.ObjectModel; using System.Linq; using System.Reactive; using System.Reactive.Disposables; +using System.Reactive.Linq; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Exceptions; +using Artemis.UI.Screens.Plugins.Help; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services.Builders; @@ -28,7 +30,8 @@ public class PluginViewModel : ActivatableViewModelBase private bool _canRemovePrerequisites; private bool _enabling; private Plugin _plugin; - private Window? _window; + private Window? _settingsWindow; + private Window? _helpWindow; public PluginViewModel(Plugin plugin, ReactiveCommand? reload, @@ -56,6 +59,7 @@ public class PluginViewModel : ActivatableViewModelBase Reload = reload; OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(vm => vm.IsEnabled, e => e && Plugin.ConfigurationDialog != null)); + OpenHelp = ReactiveCommand.Create(ExecuteOpenHelp, this.WhenAnyValue(vm => vm.HasHelpPages)); RemoveSettings = ReactiveCommand.CreateFromTask(ExecuteRemoveSettings); Remove = ReactiveCommand.CreateFromTask(ExecuteRemove); InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites)); @@ -72,13 +76,15 @@ public class PluginViewModel : ActivatableViewModelBase { Plugin.Enabled -= OnPluginToggled; Plugin.Disabled -= OnPluginToggled; - _window?.Close(); + _settingsWindow?.Close(); + _helpWindow?.Close(); }).DisposeWith(d); }); } public ReactiveCommand? Reload { get; } public ReactiveCommand OpenSettings { get; } + public ReactiveCommand OpenHelp { get; } public ReactiveCommand RemoveSettings { get; } public ReactiveCommand Remove { get; } public ReactiveCommand InstallPrerequisites { get; } @@ -102,6 +108,7 @@ public class PluginViewModel : ActivatableViewModelBase public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name; public bool IsEnabled => Plugin.IsEnabled; + public bool HasHelpPages => Plugin.HelpPages.Any(); public bool CanInstallPrerequisites { @@ -199,10 +206,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 +218,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) { @@ -221,6 +228,30 @@ public class PluginViewModel : ActivatableViewModelBase } } + private void ExecuteOpenHelp() + { + if (Plugin.ConfigurationDialog == null) + return; + + if (_helpWindow != null) + { + _helpWindow.WindowState = WindowState.Normal; + _helpWindow.Activate(); + return; + } + + try + { + _helpWindow = _windowService.ShowWindow(new PluginHelpWindowViewModel(Plugin, null)); + _helpWindow.Closed += (_, _) => _helpWindow = null; + } + catch (Exception e) + { + _windowService.ShowExceptionDialog("An exception occured while trying to show the plugin's help window", e); + throw; + } + } + private void ExecuteOpenPluginDirectory() { try @@ -313,7 +344,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 { From 949106e470879b72a0bf90f7f2705728806c264f Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 14 Apr 2023 11:55:53 +0200 Subject: [PATCH 2/3] Plugins - Added help pages --- src/Artemis.Core/Plugins/PluginInfo.cs | 3 + .../Plugins/PluginInfoHelpPage.cs | 26 ++++++++ .../Services/PluginManagementService.cs | 3 + .../Help/MarkdownPluginHelpPageView.axaml | 14 +---- .../Help/MarkdownPluginHelpPageView.axaml.cs | 3 +- .../Help/MarkdownPluginHelpPageViewModel.cs | 39 +++++++++--- .../Plugins/Help/PluginHelpWindowView.axaml | 6 +- .../Help/PluginHelpWindowView.axaml.cs | 3 +- .../Screens/Plugins/PluginView.axaml | 2 +- .../Screens/Plugins/PluginViewModel.cs | 3 - .../Settings/Updating/ReleaseView.axaml | 15 +---- src/Artemis.UI/Styles/Markdown.axaml | 60 +++++++++++++++++++ 12 files changed, 134 insertions(+), 43 deletions(-) create mode 100644 src/Artemis.Core/Plugins/PluginInfoHelpPage.cs create mode 100644 src/Artemis.UI/Styles/Markdown.axaml diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs index 6421749f8..c571518c7 100644 --- a/src/Artemis.Core/Plugins/PluginInfo.cs +++ b/src/Artemis.Core/Plugins/PluginInfo.cs @@ -188,6 +188,9 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject } } + [JsonProperty] + internal List HelpPages { get; set; } = new(); + /// /// Gets a boolean indicating whether this plugin is compatible with the current operating system and API version /// diff --git a/src/Artemis.Core/Plugins/PluginInfoHelpPage.cs b/src/Artemis.Core/Plugins/PluginInfoHelpPage.cs new file mode 100644 index 000000000..8f63c1623 --- /dev/null +++ b/src/Artemis.Core/Plugins/PluginInfoHelpPage.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; + +namespace Artemis.Core; + +/// +/// Represents basic info about a plugin and contains a reference to the instance of said plugin +/// +internal class PluginInfoHelpPage +{ + [JsonConstructor] + public PluginInfoHelpPage(string title, string markdownFile, string id) + { + Title = title; + MarkdownFile = markdownFile; + Id = id; + } + + [JsonProperty(Required = Required.Always)] + public string Title { get; } + + [JsonProperty(Required = Required.Always)] + public string MarkdownFile { get; } + + [JsonProperty(Required = Required.Always)] + public string Id { get; } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index c03aec7bb..e8a7317d3 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -350,6 +350,9 @@ internal class PluginManagementService : IPluginManagementService // Load the entity and fall back on creating a new one Plugin plugin = new(pluginInfo, directory, _pluginRepository.GetPluginByGuid(pluginInfo.Guid)); + foreach (PluginInfoHelpPage pluginInfoHelpPage in pluginInfo.HelpPages) + plugin.HelpPages.Add(new MarkdownPluginHelpPage(plugin, pluginInfoHelpPage.Title, pluginInfoHelpPage.Id, pluginInfoHelpPage.MarkdownFile)); + OnPluginLoading(new PluginEventArgs(plugin)); // Locate the main assembly entry diff --git a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml index f090b7f6f..e6441425b 100644 --- a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml +++ b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml @@ -3,25 +3,13 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:avalonia="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia" - xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins" - xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia" xmlns:help="clr-namespace:Artemis.UI.Screens.Plugins.Help" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Plugins.Help.MarkdownPluginHelpPageView" x:DataType="help:MarkdownPluginHelpPageViewModel"> - - - + diff --git a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs index dde34b97d..a151841c2 100644 --- a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs +++ b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs @@ -1,9 +1,10 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; namespace Artemis.UI.Screens.Plugins.Help; -public partial class MarkdownPluginHelpPageView : UserControl +public partial class MarkdownPluginHelpPageView : ReactiveUserControl { public MarkdownPluginHelpPageView() { diff --git a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs index 9f647b405..69dee5d11 100644 --- a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs +++ b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Reactive.Disposables; using System.Threading.Tasks; @@ -9,13 +10,23 @@ namespace Artemis.UI.Screens.Plugins.Help; public class MarkdownPluginHelpPageViewModel : ActivatableViewModelBase, IPluginHelpPage { - private string? _markdownText; private readonly MarkdownPluginHelpPage _helpPage; + private string? _markdownText; public MarkdownPluginHelpPageViewModel(MarkdownPluginHelpPage helpPage) { _helpPage = helpPage; - this.WhenActivated(d => Load().DisposeWith(d)); + this.WhenActivated(d => + { + FileSystemWatcher watcher = new(); + watcher.Path = Path.GetDirectoryName(_helpPage.MarkdownFile) ?? throw new InvalidOperationException($"Path \"{_helpPage.MarkdownFile}\" does not contain a directory"); + watcher.Filter = Path.GetFileName(_helpPage.MarkdownFile); + watcher.EnableRaisingEvents = true; + watcher.Changed += WatcherOnChanged; + watcher.DisposeWith(d); + + LoadMarkdown().DisposeWith(d); + }); } public string? MarkdownText @@ -24,6 +35,25 @@ public class MarkdownPluginHelpPageViewModel : ActivatableViewModelBase, IPlugin set => RaiseAndSetIfChanged(ref _markdownText, value); } + private async void WatcherOnChanged(object sender, FileSystemEventArgs e) + { + await LoadMarkdown(); + } + + private async Task LoadMarkdown() + { + try + { + await using FileStream stream = new(_helpPage.MarkdownFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using StreamReader reader = new(stream); + MarkdownText = await reader.ReadToEndAsync(); + } + catch (Exception e) + { + MarkdownText = e.Message; + } + } + /// public Plugin Plugin => _helpPage.Plugin; @@ -32,9 +62,4 @@ public class MarkdownPluginHelpPageViewModel : ActivatableViewModelBase, IPlugin /// public string Id => _helpPage.Id; - - private async Task Load() - { - MarkdownText ??= await File.ReadAllTextAsync(_helpPage.MarkdownFile); - } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml index 49b9551ac..0935be744 100644 --- a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml +++ b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml @@ -10,7 +10,7 @@ x:DataType="help:PluginHelpWindowViewModel" Icon="/Assets/Images/Logo/application.ico" Title="{CompiledBinding DisplayName}" - Width="800" + Width="1200" Height="800" WindowStartupLocation="CenterOwner"> @@ -18,7 +18,7 @@ - + @@ -28,4 +28,4 @@ - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs index 0ccc2a611..c821716bb 100644 --- a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs +++ b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs @@ -1,10 +1,11 @@ +using Artemis.UI.Shared; using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; namespace Artemis.UI.Screens.Plugins.Help; -public partial class PluginHelpWindowView : Window +public partial class PluginHelpWindowView : ReactiveAppWindow { public PluginHelpWindowView() { diff --git a/src/Artemis.UI/Screens/Plugins/PluginView.axaml b/src/Artemis.UI/Screens/Plugins/PluginView.axaml index cae9c18ff..605a44a59 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginView.axaml +++ b/src/Artemis.UI/Screens/Plugins/PluginView.axaml @@ -95,7 +95,7 @@ IsVisible="{CompiledBinding HasHelpPages}" Command="{CompiledBinding OpenHelp}" ToolTip.Tip="View help pages"> - + - - - + diff --git a/src/Artemis.UI/Styles/Markdown.axaml b/src/Artemis.UI/Styles/Markdown.axaml new file mode 100644 index 000000000..5374e71b0 --- /dev/null +++ b/src/Artemis.UI/Styles/Markdown.axaml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + \ No newline at end of file From 2b15657f8de3bf555f9afc0a64df32be3db66120 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 14 Apr 2023 14:43:33 +0200 Subject: [PATCH 3/3] Use a help page URL instead of doing things in-app --- .../Exceptions/ArtemisPluginException.cs | 22 ------- src/Artemis.Core/Plugins/IPluginHelpPage.cs | 22 ------- .../Plugins/MarkdownPluginHelpPage.cs | 41 ------------ src/Artemis.Core/Plugins/Plugin.cs | 5 +- src/Artemis.Core/Plugins/PluginInfo.cs | 14 +++- .../Plugins/PluginInfoHelpPage.cs | 26 -------- .../Services/PluginManagementService.cs | 3 - src/Artemis.UI/Artemis.UI.csproj | 8 --- .../Features/PluginFeatureViewModel.cs | 28 ++++---- .../Help/MarkdownPluginHelpPageView.axaml | 15 ----- .../Help/MarkdownPluginHelpPageView.axaml.cs | 18 ----- .../Help/MarkdownPluginHelpPageViewModel.cs | 65 ------------------- .../Plugins/Help/PluginHelpWindowView.axaml | 31 --------- .../Help/PluginHelpWindowView.axaml.cs | 22 ------- .../Plugins/Help/PluginHelpWindowViewModel.cs | 32 --------- .../Screens/Plugins/PluginView.axaml | 6 +- .../Screens/Plugins/PluginViewModel.cs | 55 +++++----------- 17 files changed, 46 insertions(+), 367 deletions(-) delete mode 100644 src/Artemis.Core/Plugins/IPluginHelpPage.cs delete mode 100644 src/Artemis.Core/Plugins/MarkdownPluginHelpPage.cs delete mode 100644 src/Artemis.Core/Plugins/PluginInfoHelpPage.cs delete mode 100644 src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml delete mode 100644 src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs delete mode 100644 src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml delete mode 100644 src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs delete mode 100644 src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowViewModel.cs diff --git a/src/Artemis.Core/Exceptions/ArtemisPluginException.cs b/src/Artemis.Core/Exceptions/ArtemisPluginException.cs index 6473f0293..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 helpPageId) : base(message) - { - HelpPageId = helpPageId; - } - - /// - /// Creates a new instance of the class - /// - public ArtemisPluginException(string message, Exception inner, string helpPageId) : base(message, inner) - { - HelpPageId = helpPageId; - } /// /// Gets the plugin the error is related to /// public Plugin? Plugin { get; } - - /// - /// Gets the ID of the help page to display for this exception. - /// - public string? HelpPageId { get; } - } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/IPluginHelpPage.cs b/src/Artemis.Core/Plugins/IPluginHelpPage.cs deleted file mode 100644 index 1375d6716..000000000 --- a/src/Artemis.Core/Plugins/IPluginHelpPage.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Artemis.Core; - -/// -/// Represents a plugin related help page -/// -public interface IPluginHelpPage -{ - /// - /// Gets the plugin the help page belongs to. - /// - Plugin Plugin { get; } - - /// - /// Gets the title of the help page. - /// - public string Title { get; } - - /// - /// An ID used to quickly lead users to the help page in case of an error. - /// - public string Id { get; } -} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/MarkdownPluginHelpPage.cs b/src/Artemis.Core/Plugins/MarkdownPluginHelpPage.cs deleted file mode 100644 index 09554fa29..000000000 --- a/src/Artemis.Core/Plugins/MarkdownPluginHelpPage.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.IO; - -namespace Artemis.Core; - -/// -/// Represents a plugin related help page -/// -public class MarkdownPluginHelpPage : IPluginHelpPage -{ - /// - /// Creates a new instance of the class. - /// - /// The plugin to display the markdown for. - /// A file path relative to the plugin or absolute, pointing to the markdown to display - /// The ID of the help page, used to quickly lead users to it in case of errors. - /// - public MarkdownPluginHelpPage(Plugin plugin, string title, string id, string markdownFile) - { - Plugin = plugin; - Title = title; - Id = id; - MarkdownFile = Path.IsPathRooted(markdownFile) ? markdownFile : Plugin.ResolveRelativePath(markdownFile); - - if (!File.Exists(MarkdownFile)) - throw new FileNotFoundException($"Could not find markdown file at \"{MarkdownFile}\""); - } - - /// - public Plugin Plugin { get; } - - /// - public string Title { get; } - - /// - public string Id { get; } - - /// - /// Gets the absolute path to the markdown that is to be displayed. - /// - public string MarkdownFile { get; } -} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index 290db1224..ff90181bc 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -36,7 +36,6 @@ public class Plugin : CorePropertyChanged, IDisposable Features = new ReadOnlyCollection(_features); Profilers = new ReadOnlyCollection(_profilers); - HelpPages = new List(); } /// @@ -58,9 +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; } - - public List HelpPages { get; } - + /// /// 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 c571518c7..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 @@ -188,9 +199,6 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject } } - [JsonProperty] - internal List HelpPages { get; set; } = new(); - /// /// Gets a boolean indicating whether this plugin is compatible with the current operating system and API version /// diff --git a/src/Artemis.Core/Plugins/PluginInfoHelpPage.cs b/src/Artemis.Core/Plugins/PluginInfoHelpPage.cs deleted file mode 100644 index 8f63c1623..000000000 --- a/src/Artemis.Core/Plugins/PluginInfoHelpPage.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Newtonsoft.Json; - -namespace Artemis.Core; - -/// -/// Represents basic info about a plugin and contains a reference to the instance of said plugin -/// -internal class PluginInfoHelpPage -{ - [JsonConstructor] - public PluginInfoHelpPage(string title, string markdownFile, string id) - { - Title = title; - MarkdownFile = markdownFile; - Id = id; - } - - [JsonProperty(Required = Required.Always)] - public string Title { get; } - - [JsonProperty(Required = Required.Always)] - public string MarkdownFile { get; } - - [JsonProperty(Required = Required.Always)] - public string Id { get; } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index e8a7317d3..c03aec7bb 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -350,9 +350,6 @@ internal class PluginManagementService : IPluginManagementService // Load the entity and fall back on creating a new one Plugin plugin = new(pluginInfo, directory, _pluginRepository.GetPluginByGuid(pluginInfo.Guid)); - foreach (PluginInfoHelpPage pluginInfoHelpPage in pluginInfo.HelpPages) - plugin.HelpPages.Add(new MarkdownPluginHelpPage(plugin, pluginInfoHelpPage.Title, pluginInfoHelpPage.Id, pluginInfoHelpPage.MarkdownFile)); - OnPluginLoading(new PluginEventArgs(plugin)); // Locate the main assembly entry diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index ea9845315..a1252420a 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -52,14 +52,6 @@ UpdatingTabView.axaml Code - - MarkdownPluginHelpPageView.axaml - Code - - - PluginHelpWindowView.axaml - Code - PluginFeatureView.axaml Code diff --git a/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureViewModel.cs b/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureViewModel.cs index 2df1c64a9..2d83d9e6c 100644 --- a/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureViewModel.cs +++ b/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureViewModel.cs @@ -23,7 +23,6 @@ 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/Help/MarkdownPluginHelpPageView.axaml b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml deleted file mode 100644 index e6441425b..000000000 --- a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - diff --git a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs deleted file mode 100644 index a151841c2..000000000 --- a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Markup.Xaml; -using Avalonia.ReactiveUI; - -namespace Artemis.UI.Screens.Plugins.Help; - -public partial class MarkdownPluginHelpPageView : ReactiveUserControl -{ - public MarkdownPluginHelpPageView() - { - InitializeComponent(); - } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs deleted file mode 100644 index 69dee5d11..000000000 --- a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.IO; -using System.Reactive.Disposables; -using System.Threading.Tasks; -using Artemis.Core; -using Artemis.UI.Shared; -using ReactiveUI; - -namespace Artemis.UI.Screens.Plugins.Help; - -public class MarkdownPluginHelpPageViewModel : ActivatableViewModelBase, IPluginHelpPage -{ - private readonly MarkdownPluginHelpPage _helpPage; - private string? _markdownText; - - public MarkdownPluginHelpPageViewModel(MarkdownPluginHelpPage helpPage) - { - _helpPage = helpPage; - this.WhenActivated(d => - { - FileSystemWatcher watcher = new(); - watcher.Path = Path.GetDirectoryName(_helpPage.MarkdownFile) ?? throw new InvalidOperationException($"Path \"{_helpPage.MarkdownFile}\" does not contain a directory"); - watcher.Filter = Path.GetFileName(_helpPage.MarkdownFile); - watcher.EnableRaisingEvents = true; - watcher.Changed += WatcherOnChanged; - watcher.DisposeWith(d); - - LoadMarkdown().DisposeWith(d); - }); - } - - public string? MarkdownText - { - get => _markdownText; - set => RaiseAndSetIfChanged(ref _markdownText, value); - } - - private async void WatcherOnChanged(object sender, FileSystemEventArgs e) - { - await LoadMarkdown(); - } - - private async Task LoadMarkdown() - { - try - { - await using FileStream stream = new(_helpPage.MarkdownFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using StreamReader reader = new(stream); - MarkdownText = await reader.ReadToEndAsync(); - } - catch (Exception e) - { - MarkdownText = e.Message; - } - } - - /// - public Plugin Plugin => _helpPage.Plugin; - - /// - public string Title => _helpPage.Title; - - /// - public string Id => _helpPage.Id; -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml deleted file mode 100644 index 0935be744..000000000 --- a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs deleted file mode 100644 index c821716bb..000000000 --- a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Artemis.UI.Shared; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; - -namespace Artemis.UI.Screens.Plugins.Help; - -public partial class PluginHelpWindowView : ReactiveAppWindow -{ - public PluginHelpWindowView() - { - InitializeComponent(); -#if DEBUG - this.AttachDevTools(); -#endif - } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowViewModel.cs b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowViewModel.cs deleted file mode 100644 index b9ad2b37c..000000000 --- a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowViewModel.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.ObjectModel; -using System.Linq; -using Artemis.Core; -using Artemis.UI.Shared; - -namespace Artemis.UI.Screens.Plugins.Help; - -public class PluginHelpWindowViewModel : ActivatableViewModelBase -{ - private IPluginHelpPage? _selectedHelpPage; - - public PluginHelpWindowViewModel(Plugin plugin, string? preselectId) - { - Plugin = plugin; - DisplayName = $"{Plugin.Info.Name} | Help"; - - // Populate help pages by wrapping MarkdownHelpPages into a MarkdownHelpPageViewModel - // other types are used directly, up to them to implement a VM directly as well - HelpPages = new ReadOnlyCollection(plugin.HelpPages.Select(p => p is MarkdownPluginHelpPage m ? new MarkdownPluginHelpPageViewModel(m) : p).ToList()); - - _selectedHelpPage = preselectId != null ? HelpPages.FirstOrDefault(p => p.Id == preselectId) : HelpPages.FirstOrDefault(); - } - - public Plugin Plugin { get; } - public ReadOnlyCollection HelpPages { get; } - - public IPluginHelpPage? SelectedHelpPage - { - get => _selectedHelpPage; - set => RaiseAndSetIfChanged(ref _selectedHelpPage, value); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/PluginView.axaml b/src/Artemis.UI/Screens/Plugins/PluginView.axaml index 605a44a59..640279366 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginView.axaml +++ b/src/Artemis.UI/Screens/Plugins/PluginView.axaml @@ -92,9 +92,9 @@ + IsVisible="{CompiledBinding Plugin.Info.HelpPage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" + NavigateUri="{CompiledBinding Plugin.Info.HelpPage}" + ToolTip.Tip="{CompiledBinding Plugin.Info.HelpPage}"> ? reload, @@ -59,7 +56,6 @@ public class PluginViewModel : ActivatableViewModelBase Reload = reload; OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(vm => vm.IsEnabled, e => e && Plugin.ConfigurationDialog != null)); - OpenHelp = ReactiveCommand.Create(ExecuteOpenHelp, this.WhenAnyValue(vm => vm.HasHelpPages)); RemoveSettings = ReactiveCommand.CreateFromTask(ExecuteRemoveSettings); Remove = ReactiveCommand.CreateFromTask(ExecuteRemove); InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites)); @@ -77,14 +73,12 @@ public class PluginViewModel : ActivatableViewModelBase Plugin.Enabled -= OnPluginToggled; Plugin.Disabled -= OnPluginToggled; _settingsWindow?.Close(); - _helpWindow?.Close(); }).DisposeWith(d); }); } public ReactiveCommand? Reload { get; } public ReactiveCommand OpenSettings { get; } - public ReactiveCommand OpenHelp { get; } public ReactiveCommand RemoveSettings { get; } public ReactiveCommand Remove { get; } public ReactiveCommand InstallPrerequisites { get; } @@ -108,7 +102,6 @@ public class PluginViewModel : ActivatableViewModelBase public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name; public bool IsEnabled => Plugin.IsEnabled; - public bool HasHelpPages => Plugin.HelpPages.Any(); public bool CanInstallPrerequisites { @@ -135,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 { @@ -174,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 { @@ -187,7 +172,6 @@ public class PluginViewModel : ActivatableViewModelBase } } - public void CheckPrerequisites() { CanInstallPrerequisites = false; @@ -228,27 +212,6 @@ public class PluginViewModel : ActivatableViewModelBase } } - private void ExecuteOpenHelp() - { - if (_helpWindow != null) - { - _helpWindow.WindowState = WindowState.Normal; - _helpWindow.Activate(); - return; - } - - try - { - _helpWindow = _windowService.ShowWindow(new PluginHelpWindowViewModel(Plugin, null)); - _helpWindow.Closed += (_, _) => _helpWindow = null; - } - catch (Exception e) - { - _windowService.ShowExceptionDialog("An exception occured while trying to show the plugin's help window", e); - throw; - } - } - private void ExecuteOpenPluginDirectory() { try @@ -334,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) {