diff --git a/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs b/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs
index 40863ce25..e761b7a3e 100644
--- a/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs
+++ b/src/Artemis.Core/Plugins/PluginFeatureAttribute.cs
@@ -26,6 +26,7 @@ namespace Artemis.Core
///
/// Marks the feature to always be enabled as long as the plugin is enabled
+ /// Note: always if this is the plugin's only feature
///
public bool AlwaysEnabled { get; set; }
}
diff --git a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs
index 62012d9a4..c0350c26f 100644
--- a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs
+++ b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs
@@ -122,10 +122,11 @@ namespace Artemis.Core
}
///
- /// Marks the feature to always be enabled as long as the plugin is enabled and cannot be disabled
+ /// Marks the feature to always be enabled as long as the plugin is enabled and cannot be disabled.
+ /// Note: always if this is the plugin's only feature
///
[JsonProperty]
- public bool AlwaysEnabled { get; }
+ public bool AlwaysEnabled { get; internal set; }
///
/// Gets a boolean indicating whether the feature is enabled in persistent storage
diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs
index 2486544cc..e0850b3a0 100644
--- a/src/Artemis.Core/Plugins/PluginInfo.cs
+++ b/src/Artemis.Core/Plugins/PluginInfo.cs
@@ -24,7 +24,7 @@ namespace Artemis.Core
private Plugin _plugin = null!;
private Version _version = null!;
private bool _requiresAdmin;
-
+ private PluginPlatform? _platforms;
internal PluginInfo()
{
@@ -142,7 +142,17 @@ namespace Artemis.Core
get => _requiresAdmin;
internal set => SetAndNotify(ref _requiresAdmin, value);
}
-
+
+ ///
+ /// Gets
+ ///
+ [JsonProperty]
+ public PluginPlatform? Platforms
+ {
+ get => _platforms;
+ internal set => _platforms = value;
+ }
+
///
/// Gets the plugin this info is associated with
///
@@ -164,6 +174,11 @@ namespace Artemis.Core
return Icon.Contains('.') ? Plugin.ResolveRelativePath(Icon) : Icon;
}
}
+
+ ///
+ /// Gets a boolean indicating whether this plugin is compatible with the current operating system
+ ///
+ public bool IsCompatible => Platforms.MatchesCurrentOperatingSystem();
internal string PreferredPluginDirectory => $"{Main.Split(".dll")[0].Replace("/", "").Replace("\\", "")}-{Guid.ToString().Substring(0, 8)}";
diff --git a/src/Artemis.Core/Plugins/PluginPlatform.cs b/src/Artemis.Core/Plugins/PluginPlatform.cs
index 5d44e7943..fa07d2646 100644
--- a/src/Artemis.Core/Plugins/PluginPlatform.cs
+++ b/src/Artemis.Core/Plugins/PluginPlatform.cs
@@ -1,4 +1,5 @@
using System;
+using Newtonsoft.Json;
namespace Artemis.Core;
@@ -6,15 +7,36 @@ namespace Artemis.Core;
/// Specifies OS platforms a plugin may support.
///
[Flags]
+[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public enum PluginPlatform
{
/// The Windows platform.
- Windows = 0,
+ Windows = 1,
/// The Linux platform.
- Linux = 1,
+ Linux = 2,
/// The OSX platform.
// ReSharper disable once InconsistentNaming
- OSX = 2
+ OSX = 4
+}
+
+internal static class PluginPlatformExtensions
+{
+ ///
+ /// Determines whether the provided platform matches the current operating system.
+ ///
+ internal static bool MatchesCurrentOperatingSystem(this PluginPlatform? platform)
+ {
+ if (platform == null)
+ return true;
+
+ if (OperatingSystem.IsWindows())
+ return platform.Value.HasFlag(PluginPlatform.Windows);
+ if (OperatingSystem.IsLinux())
+ return platform.Value.HasFlag(PluginPlatform.Linux);
+ if (OperatingSystem.IsMacOS())
+ return platform.Value.HasFlag(PluginPlatform.OSX);
+ return false;
+ }
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs b/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs
index edb408093..f4bc30b79 100644
--- a/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs
+++ b/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs
@@ -101,16 +101,7 @@ namespace Artemis.Core
///
public bool AppliesToPlatform()
{
- if (Platform == null)
- return true;
-
- if (OperatingSystem.IsWindows())
- return Platform.Value.HasFlag(PluginPlatform.Windows);
- if (OperatingSystem.IsLinux())
- return Platform.Value.HasFlag(PluginPlatform.Linux);
- if (OperatingSystem.IsMacOS())
- return Platform.Value.HasFlag(PluginPlatform.OSX);
- return false;
+ return Platform.MatchesCurrentOperatingSystem();
}
///
diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs
index 4623bd948..48e403954 100644
--- a/src/Artemis.Core/Services/PluginManagementService.cs
+++ b/src/Artemis.Core/Services/PluginManagementService.cs
@@ -260,7 +260,7 @@ namespace Artemis.Core.Services
}
}
- foreach (Plugin plugin in _plugins.Where(p => p.Entity.IsEnabled))
+ foreach (Plugin plugin in _plugins.Where(p => p.Info.IsCompatible && p.Entity.IsEnabled))
{
try
{
@@ -364,7 +364,13 @@ namespace Artemis.Core.Services
// Load the enabled state and if not found, default to true
PluginFeatureEntity featureEntity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureType.FullName) ??
new PluginFeatureEntity {IsEnabled = true, Type = featureType.FullName!};
- plugin.AddFeature(new PluginFeatureInfo(plugin, featureType, featureEntity, (PluginFeatureAttribute?) Attribute.GetCustomAttribute(featureType, typeof(PluginFeatureAttribute))));
+ PluginFeatureInfo feature = new(plugin, featureType, featureEntity, (PluginFeatureAttribute?) Attribute.GetCustomAttribute(featureType, typeof(PluginFeatureAttribute)));
+
+ // If the plugin only has a single feature, it should always be enabled
+ if (featureTypes.Count == 1)
+ feature.AlwaysEnabled = true;
+
+ plugin.AddFeature(feature);
}
if (!featureTypes.Any())
@@ -390,6 +396,9 @@ namespace Artemis.Core.Services
public void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock)
{
+ if (!plugin.Info.IsCompatible)
+ throw new ArtemisPluginException(plugin, $"This plugin only supports the following operating system(s): {plugin.Info.Platforms}");
+
if (plugin.Assembly == null)
throw new ArtemisPluginException(plugin, "Cannot enable a plugin that hasn't successfully been loaded");
@@ -446,7 +455,18 @@ namespace Artemis.Core.Services
// Activate features after they are all loaded
foreach (PluginFeatureInfo pluginFeature in plugin.Features.Where(f => f.Instance != null && (f.EnabledInStorage || f.AlwaysEnabled)))
- EnablePluginFeature(pluginFeature.Instance!, false, !ignorePluginLock);
+ {
+ try
+ {
+ EnablePluginFeature(pluginFeature.Instance!, false, !ignorePluginLock);
+ }
+ catch (Exception)
+ {
+ if (pluginFeature.AlwaysEnabled)
+ DisablePlugin(plugin, false);
+ throw;
+ }
+ }
if (saveState)
{
diff --git a/src/Artemis.UI.Linux/App.axaml.cs b/src/Artemis.UI.Linux/App.axaml.cs
index 95c65051e..143078616 100644
--- a/src/Artemis.UI.Linux/App.axaml.cs
+++ b/src/Artemis.UI.Linux/App.axaml.cs
@@ -1,6 +1,7 @@
using Artemis.Core.Services;
using Artemis.UI.Linux.Providers.Input;
using Avalonia;
+using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using ReactiveUI;
@@ -26,6 +27,9 @@ namespace Artemis.UI.Linux
public override void OnFrameworkInitializationCompleted()
{
+ if (Design.IsDesignMode)
+ return;
+
ArtemisBootstrapper.Initialize();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
_applicationStateManager = new ApplicationStateManager(_kernel!, desktop.Args);
diff --git a/src/Artemis.UI.MacOS/App.axaml.cs b/src/Artemis.UI.MacOS/App.axaml.cs
index 4993d81c9..42a066f61 100644
--- a/src/Artemis.UI.MacOS/App.axaml.cs
+++ b/src/Artemis.UI.MacOS/App.axaml.cs
@@ -1,4 +1,5 @@
using Avalonia;
+using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using Ninject;
@@ -20,6 +21,9 @@ namespace Artemis.UI.MacOS
public override void OnFrameworkInitializationCompleted()
{
+ if (Design.IsDesignMode)
+ return;
+
ArtemisBootstrapper.Initialize();
}
}
diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj
index 88ec4b2c3..a034b7833 100644
--- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj
+++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj
@@ -3,8 +3,8 @@
Library
net6.0
enable
-
-
+
+
bin\
x64
x64
@@ -15,20 +15,20 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/src/Artemis.UI/ReactiveCoreWindow.cs b/src/Artemis.UI.Shared/ReactiveCoreWindow.cs
similarity index 98%
rename from src/Artemis.UI/ReactiveCoreWindow.cs
rename to src/Artemis.UI.Shared/ReactiveCoreWindow.cs
index c8325b036..9c82b0afd 100644
--- a/src/Artemis.UI/ReactiveCoreWindow.cs
+++ b/src/Artemis.UI.Shared/ReactiveCoreWindow.cs
@@ -4,7 +4,7 @@ using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
-namespace Artemis.UI
+namespace Artemis.UI.Shared
{
///
/// A ReactiveUI that implements the interface and will
diff --git a/src/Artemis.UI.Shared/Services/Builders/OpenFolderDialogBuilder.cs b/src/Artemis.UI.Shared/Services/Builders/OpenFolderDialogBuilder.cs
new file mode 100644
index 000000000..47846d170
--- /dev/null
+++ b/src/Artemis.UI.Shared/Services/Builders/OpenFolderDialogBuilder.cs
@@ -0,0 +1,54 @@
+using System.Threading.Tasks;
+using Avalonia.Controls;
+
+namespace Artemis.UI.Shared.Services.Builders;
+
+///
+/// Represents a builder that can create a .
+///
+public class OpenFolderDialogBuilder
+{
+ private readonly OpenFolderDialog _openFolderDialog;
+ private readonly Window _parent;
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The parent window that will host the dialog.
+ internal OpenFolderDialogBuilder(Window parent)
+ {
+ _parent = parent;
+ _openFolderDialog = new OpenFolderDialog();
+ }
+
+
+ ///
+ /// Set the title of the dialog
+ ///
+ public OpenFolderDialogBuilder WithTitle(string? title)
+ {
+ _openFolderDialog.Title = title;
+ return this;
+ }
+
+ ///
+ /// Set the initial directory of the dialog
+ ///
+ public OpenFolderDialogBuilder WithDirectory(string? directory)
+ {
+ _openFolderDialog.Directory = directory;
+ return this;
+ }
+
+ ///
+ /// Asynchronously shows the folder dialog.
+ ///
+ ///
+ /// A task that on completion returns an array containing the full path to the selected
+ /// folder, or null if the dialog was canceled.
+ ///
+ public async Task ShowAsync()
+ {
+ return await _openFolderDialog.ShowAsync(_parent);
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs
index 06efe5882..f10d6c2fc 100644
--- a/src/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs
+++ b/src/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs
@@ -22,7 +22,7 @@ namespace Artemis.UI.Shared.Services
/// Given a ViewModel, show its corresponding View as a window
///
/// ViewModel to show the View for
- void ShowWindow(object viewModel);
+ Window ShowWindow(object viewModel);
///
/// Shows a dialog displaying the given exception
@@ -61,6 +61,12 @@ namespace Artemis.UI.Shared.Services
///
Task ShowConfirmContentDialog(string title, string message, string confirm = "Confirm", string? cancel = "Cancel");
+ ///
+ /// Creates an open folder dialog, use the fluent API to configure it
+ ///
+ /// The builder that can be used to configure the dialog
+ OpenFolderDialogBuilder CreateOpenFolderDialog();
+
///
/// Creates an open file dialog, use the fluent API to configure it
///
diff --git a/src/Artemis.UI.Shared/Services/Window/WindowService.cs b/src/Artemis.UI.Shared/Services/Window/WindowService.cs
index 7621d84c0..5348f2574 100644
--- a/src/Artemis.UI.Shared/Services/Window/WindowService.cs
+++ b/src/Artemis.UI.Shared/Services/Window/WindowService.cs
@@ -30,7 +30,7 @@ namespace Artemis.UI.Shared.Services
return viewModel;
}
- public void ShowWindow(object viewModel)
+ public Window ShowWindow(object viewModel)
{
Window? parent = GetCurrentWindow();
@@ -49,6 +49,8 @@ namespace Artemis.UI.Shared.Services
window.Show(parent);
else
window.Show();
+
+ return window;
}
public async Task ShowDialogAsync(params (string name, object? value)[] parameters) where TViewModel : DialogViewModelBase
@@ -86,7 +88,6 @@ namespace Artemis.UI.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(parent);
}
@@ -118,6 +119,14 @@ namespace Artemis.UI.Shared.Services
throw new ArtemisSharedUIException("Can't show a content dialog without any windows being shown.");
return new ContentDialogBuilder(_kernel, currentWindow);
}
+
+ public OpenFolderDialogBuilder CreateOpenFolderDialog()
+ {
+ Window? currentWindow = GetCurrentWindow();
+ if (currentWindow == null)
+ throw new ArtemisSharedUIException("Can't show an open folder dialog without any windows being shown.");
+ return new OpenFolderDialogBuilder(currentWindow);
+ }
public OpenFileDialogBuilder CreateOpenFileDialog()
{
diff --git a/src/Artemis.UI.Shared/Styles/Notifications.axaml b/src/Artemis.UI.Shared/Styles/Notifications.axaml
index bfe195ede..0e9553ca2 100644
--- a/src/Artemis.UI.Shared/Styles/Notifications.axaml
+++ b/src/Artemis.UI.Shared/Styles/Notifications.axaml
@@ -19,7 +19,7 @@
-
+
diff --git a/src/Artemis.UI.Shared/ViewModelBase.cs b/src/Artemis.UI.Shared/ViewModelBase.cs
index 7f7a0dca0..24a4d2a11 100644
--- a/src/Artemis.UI.Shared/ViewModelBase.cs
+++ b/src/Artemis.UI.Shared/ViewModelBase.cs
@@ -51,17 +51,8 @@ public abstract class DialogViewModelBase : ValidatableViewModelBase
{
CloseRequested?.Invoke(this, new DialogClosedEventArgs(result));
}
-
- ///
- /// Closes the dialog without a result
- ///
- public void Cancel()
- {
- CancelRequested?.Invoke(this, EventArgs.Empty);
- }
-
+
internal event EventHandler>? CloseRequested;
- internal event EventHandler? CancelRequested;
}
///
diff --git a/src/Artemis.UI.Windows/App.axaml.cs b/src/Artemis.UI.Windows/App.axaml.cs
index 94834a5ac..1f2e213d2 100644
--- a/src/Artemis.UI.Windows/App.axaml.cs
+++ b/src/Artemis.UI.Windows/App.axaml.cs
@@ -2,6 +2,7 @@ using Artemis.Core.Services;
using Artemis.UI.Windows.Ninject;
using Artemis.UI.Windows.Providers.Input;
using Avalonia;
+using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
@@ -22,9 +23,9 @@ namespace Artemis.UI.Windows
public override void OnFrameworkInitializationCompleted()
{
- if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+ if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode)
return;
-
+
ArtemisBootstrapper.Initialize();
_applicationStateManager = new ApplicationStateManager(_kernel!, desktop.Args);
RegisterProviders(_kernel!);
diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj
index 92065dd99..081646711 100644
--- a/src/Artemis.UI/Artemis.UI.csproj
+++ b/src/Artemis.UI/Artemis.UI.csproj
@@ -8,44 +8,44 @@
x64
-
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
diff --git a/src/Artemis.UI/MainWindow.axaml.cs b/src/Artemis.UI/MainWindow.axaml.cs
index 77be8f698..666d7cdb0 100644
--- a/src/Artemis.UI/MainWindow.axaml.cs
+++ b/src/Artemis.UI/MainWindow.axaml.cs
@@ -1,5 +1,6 @@
using System;
using Artemis.UI.Screens.Root;
+using Artemis.UI.Shared;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
diff --git a/src/Artemis.UI/Screens/Debugger/DebugView.axaml.cs b/src/Artemis.UI/Screens/Debugger/DebugView.axaml.cs
index 0709047e5..7b03c0a46 100644
--- a/src/Artemis.UI/Screens/Debugger/DebugView.axaml.cs
+++ b/src/Artemis.UI/Screens/Debugger/DebugView.axaml.cs
@@ -1,6 +1,7 @@
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
+using Artemis.UI.Shared;
using Artemis.UI.Shared.Events;
using Avalonia;
using Avalonia.Controls;
diff --git a/src/Artemis.UI/Screens/Plugins/PluginFeatureView.axaml b/src/Artemis.UI/Screens/Plugins/PluginFeatureView.axaml
index 10138d3fa..c39c97888 100644
--- a/src/Artemis.UI/Screens/Plugins/PluginFeatureView.axaml
+++ b/src/Artemis.UI/Screens/Plugins/PluginFeatureView.axaml
@@ -4,8 +4,10 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
- x:Class="Artemis.UI.Screens.Plugins.PluginFeatureView">
+ x:Class="Artemis.UI.Screens.Plugins.PluginFeatureView"
+ x:DataType="plugins:PluginFeatureViewModel">
@@ -24,14 +26,15 @@
+ IsVisible="{CompiledBinding LoadException, Converter={x:Static ObjectConverters.IsNull}}" />