1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-01-02 02:33:32 +00:00

Plugins - Ported prerequisites UI

Scripting - Ported scripting UI
This commit is contained in:
Robert 2022-07-10 23:25:34 +02:00
parent 0256a0d625
commit 3b4194cb9d
56 changed files with 1301 additions and 274 deletions

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Artemis.Core.ScriptingProviders; using Artemis.Core.ScriptingProviders;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
@ -15,19 +16,24 @@ namespace Artemis.Core
private readonly object _lock = new(); private readonly object _lock = new();
private bool _isFreshImport; private bool _isFreshImport;
private ProfileElement? _lastSelectedProfileElement; private ProfileElement? _lastSelectedProfileElement;
private readonly ObservableCollection<ProfileScript> _scripts;
private readonly ObservableCollection<ScriptConfiguration> _scriptConfigurations;
internal Profile(ProfileConfiguration configuration, ProfileEntity profileEntity) : base(null!) internal Profile(ProfileConfiguration configuration, ProfileEntity profileEntity) : base(null!)
{ {
_scripts = new ObservableCollection<ProfileScript>();
_scriptConfigurations = new ObservableCollection<ScriptConfiguration>();
Configuration = configuration; Configuration = configuration;
Profile = this; Profile = this;
ProfileEntity = profileEntity; ProfileEntity = profileEntity;
EntityId = profileEntity.Id; EntityId = profileEntity.Id;
Scripts = new List<ProfileScript>();
ScriptConfigurations = new List<ScriptConfiguration>();
UndoStack = new MaxStack<string>(20); UndoStack = new MaxStack<string>(20);
RedoStack = new MaxStack<string>(20); RedoStack = new MaxStack<string>(20);
Exceptions = new List<Exception>(); Exceptions = new List<Exception>();
Scripts = new ReadOnlyObservableCollection<ProfileScript>(_scripts);
ScriptConfigurations = new ReadOnlyObservableCollection<ScriptConfiguration>(_scriptConfigurations);
Load(); Load();
} }
@ -40,12 +46,12 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets a collection of all active scripts assigned to this profile /// Gets a collection of all active scripts assigned to this profile
/// </summary> /// </summary>
public List<ProfileScript> Scripts { get; } public ReadOnlyObservableCollection<ProfileScript> Scripts { get; }
/// <summary> /// <summary>
/// Gets a collection of all script configurations assigned to this profile /// Gets a collection of all script configurations assigned to this profile
/// </summary> /// </summary>
public List<ScriptConfiguration> ScriptConfigurations { get; } public ReadOnlyObservableCollection<ScriptConfiguration> ScriptConfigurations { get; }
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether this profile is freshly imported i.e. no changes have been made to it /// Gets or sets a boolean indicating whether this profile is freshly imported i.e. no changes have been made to it
@ -169,8 +175,8 @@ namespace Artemis.Core
if (!disposing) if (!disposing)
return; return;
while (Scripts.Count > 1) while (Scripts.Count > 0)
Scripts[0].Dispose(); RemoveScript(Scripts[0]);
foreach (ProfileElement profileElement in Children) foreach (ProfileElement profileElement in Children)
profileElement.Dispose(); profileElement.Dispose();
@ -208,16 +214,62 @@ namespace Artemis.Core
else else
LastSelectedProfileElement = null; LastSelectedProfileElement = null;
foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations) while (_scriptConfigurations.Any())
scriptConfiguration.Script?.Dispose(); RemoveScriptConfiguration(_scriptConfigurations[0]);
ScriptConfigurations.Clear(); foreach (ScriptConfiguration scriptConfiguration in ProfileEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e)))
ScriptConfigurations.AddRange(ProfileEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e))); AddScriptConfiguration(scriptConfiguration);
// Load node scripts last since they may rely on the profile structure being in place // Load node scripts last since they may rely on the profile structure being in place
foreach (RenderProfileElement renderProfileElement in renderElements) foreach (RenderProfileElement renderProfileElement in renderElements)
renderProfileElement.LoadNodeScript(); renderProfileElement.LoadNodeScript();
} }
/// <summary>
/// Removes a script configuration from the profile, if the configuration has an active script it is also removed.
/// </summary>
internal void RemoveScriptConfiguration(ScriptConfiguration scriptConfiguration)
{
if (!_scriptConfigurations.Contains(scriptConfiguration))
return;
Script? script = scriptConfiguration.Script;
if (script != null)
RemoveScript((ProfileScript) script);
_scriptConfigurations.Remove(scriptConfiguration);
}
/// <summary>
/// Adds a script configuration to the profile but does not instantiate it's script.
/// </summary>
internal void AddScriptConfiguration(ScriptConfiguration scriptConfiguration)
{
if (!_scriptConfigurations.Contains(scriptConfiguration))
_scriptConfigurations.Add(scriptConfiguration);
}
/// <summary>
/// Adds a script that has a script configuration belonging to this profile.
/// </summary>
internal void AddScript(ProfileScript script)
{
if (!_scriptConfigurations.Contains(script.ScriptConfiguration))
throw new ArtemisCoreException("Cannot add a script to a profile whose script configuration doesn't belong to the same profile.");
if (!_scripts.Contains(script))
_scripts.Add(script);
}
/// <summary>
/// Removes a script from the profile and disposes it.
/// </summary>
internal void RemoveScript(ProfileScript script)
{
_scripts.Remove(script);
script.Dispose();
}
internal override void Save() internal override void Save()
{ {
if (Disposed) if (Disposed)

View File

@ -166,10 +166,13 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public List<PluginPrerequisite> Prerequisites { get; } = new(); public List<PluginPrerequisite> Prerequisites { get; } = new();
/// <inheritdoc />
public IEnumerable<PluginPrerequisite> PlatformPrerequisites => Prerequisites.Where(p => p.AppliesToPlatform());
/// <inheritdoc /> /// <inheritdoc />
public bool ArePrerequisitesMet() public bool ArePrerequisitesMet()
{ {
return Prerequisites.All(p => p.IsMet()); return PlatformPrerequisites.All(p => p.IsMet());
} }
} }
} }

View File

@ -176,10 +176,13 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public List<PluginPrerequisite> Prerequisites { get; } = new(); public List<PluginPrerequisite> Prerequisites { get; } = new();
/// <inheritdoc />
public IEnumerable<PluginPrerequisite> PlatformPrerequisites => Prerequisites.Where(p => p.AppliesToPlatform());
/// <inheritdoc /> /// <inheritdoc />
public bool ArePrerequisitesMet() public bool ArePrerequisitesMet()
{ {
return Prerequisites.All(p => p.IsMet()); return PlatformPrerequisites.All(p => p.IsMet());
} }
} }
} }

View File

@ -0,0 +1,20 @@
using System;
namespace Artemis.Core;
/// <summary>
/// Specifies OS platforms a plugin may support.
/// </summary>
[Flags]
public enum PluginPlatform
{
/// <summary>The Windows platform.</summary>
Windows = 0,
/// <summary>The Linux platform.</summary>
Linux = 1,
/// <summary>The OSX platform.</summary>
// ReSharper disable once InconsistentNaming
OSX = 2
}

View File

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace Artemis.Core namespace Artemis.Core
{ {
@ -13,6 +12,11 @@ namespace Artemis.Core
/// </summary> /// </summary>
List<PluginPrerequisite> Prerequisites { get; } List<PluginPrerequisite> Prerequisites { get; }
/// <summary>
/// Gets a list of prerequisites of the current platform for this plugin
/// </summary>
IEnumerable<PluginPrerequisite> PlatformPrerequisites { get; }
/// <summary> /// <summary>
/// Determines whether the prerequisites of this plugin are met /// Determines whether the prerequisites of this plugin are met
/// </summary> /// </summary>

View File

@ -22,6 +22,11 @@ namespace Artemis.Core
/// </summary> /// </summary>
public abstract string Description { get; } public abstract string Description { get; }
/// <summary>
/// Gets or sets the platform(s) this prerequisite applies to.
/// </summary>
public PluginPlatform? Platform { get; protected set; }
/// <summary> /// <summary>
/// Gets a list of actions to execute when <see cref="Install" /> is called /// Gets a list of actions to execute when <see cref="Install" /> is called
/// </summary> /// </summary>
@ -91,6 +96,23 @@ namespace Artemis.Core
/// <returns><see langword="true" /> if the prerequisite is met; otherwise <see langword="false" /></returns> /// <returns><see langword="true" /> if the prerequisite is met; otherwise <see langword="false" /></returns>
public abstract bool IsMet(); public abstract bool IsMet();
/// <summary>
/// Determines whether this prerequisite applies to the current operating system.
/// </summary>
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;
}
/// <summary> /// <summary>
/// Called before installation starts /// Called before installation starts
/// </summary> /// </summary>

View File

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -36,6 +38,11 @@ namespace Artemis.Core
/// </summary> /// </summary>
public string Target { get; } public string Target { get; }
/// <summary>
/// Gets or sets an optional list of files to extract, if <see langword="null"/> all files will be extracted.
/// </summary>
public List<string>? FilesToExtract { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public override async Task Execute(CancellationToken cancellationToken) public override async Task Execute(CancellationToken cancellationToken)
{ {
@ -50,10 +57,15 @@ namespace Artemis.Core
{ {
ZipArchive archive = new(fileStream); ZipArchive archive = new(fileStream);
long count = 0; long count = 0;
foreach (ZipArchiveEntry entry in archive.Entries)
List<ZipArchiveEntry> entries = new(archive.Entries);
if (FilesToExtract != null)
entries = entries.Where(e => FilesToExtract.Contains(e.FullName)).ToList();
foreach (ZipArchiveEntry entry in entries)
{ {
await using Stream unzippedEntryStream = entry.Open(); await using Stream unzippedEntryStream = entry.Open();
Progress.Report((count, archive.Entries.Count)); Progress.Report((count, entries.Count));
if (entry.Length > 0) if (entry.Length > 0)
{ {
string path = Path.Combine(Target, entry.FullName); string path = Path.Combine(Target, entry.FullName);

View File

@ -18,11 +18,13 @@ namespace Artemis.Core.ScriptingProviders
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="ScriptConfiguration" /> class /// Creates a new instance of the <see cref="ScriptConfiguration" /> class
/// </summary> /// </summary>
public ScriptConfiguration(ScriptingProvider provider, string name) public ScriptConfiguration(ScriptingProvider provider, string name, ScriptType scriptType)
{ {
_scriptingProviderId = provider.Id; _scriptingProviderId = provider.Id;
_name = name; _name = name;
Entity = new ScriptConfigurationEntity(); Entity = new ScriptConfigurationEntity();
PendingScriptContent = provider.GetDefaultScriptContent(scriptType);
ScriptContent = PendingScriptContent;
} }
internal ScriptConfiguration(ScriptConfigurationEntity entity) internal ScriptConfiguration(ScriptConfigurationEntity entity)

View File

@ -71,5 +71,11 @@ namespace Artemis.Core.ScriptingProviders
/// </summary> /// </summary>
/// <param name="scriptType">The type of script the editor will host</param> /// <param name="scriptType">The type of script the editor will host</param>
public abstract IScriptEditorViewModel CreateScriptEditor(ScriptType scriptType); public abstract IScriptEditorViewModel CreateScriptEditor(ScriptType scriptType);
/// <summary>
/// Called when a script for a certain type needs default content.
/// </summary>
/// <param name="scriptType">The type of script the default content is for.</param>
public abstract string GetDefaultScriptContent(ScriptType scriptType);
} }
} }

View File

@ -38,7 +38,7 @@ namespace Artemis.Core.ScriptingProviders
/// <inheritdoc /> /// <inheritdoc />
internal override void InternalCleanup() internal override void InternalCleanup()
{ {
ScriptingService?.InternalGlobalScripts.Remove(this); ScriptingService?.RemoveScript(ScriptConfiguration);
} }
#endregion #endregion

View File

@ -60,10 +60,7 @@ namespace Artemis.Core.ScriptingProviders
/// <inheritdoc /> /// <inheritdoc />
internal override void InternalCleanup() internal override void InternalCleanup()
{ {
lock (Profile.Scripts) Profile.RemoveScript(this);
{
Profile.Scripts.Remove(this);
}
} }
#endregion #endregion

View File

@ -9,6 +9,7 @@ namespace Artemis.Core.ScriptingProviders
public abstract class Script : CorePropertyChanged, IDisposable public abstract class Script : CorePropertyChanged, IDisposable
{ {
private ScriptingProvider _scriptingProvider = null!; private ScriptingProvider _scriptingProvider = null!;
private bool _disposed;
/// <summary> /// <summary>
/// The base constructor of any script /// The base constructor of any script
@ -71,6 +72,10 @@ namespace Artemis.Core.ScriptingProviders
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
if (_disposed)
return;
_disposed = true;
ScriptConfiguration.PropertyChanged -= ScriptConfigurationOnPropertyChanged; ScriptConfiguration.PropertyChanged -= ScriptConfigurationOnPropertyChanged;
ScriptConfiguration.Script = null; ScriptConfiguration.Script = null;
ScriptingProvider.InternalScripts.Remove(this); ScriptingProvider.InternalScripts.Remove(this);

View File

@ -8,33 +8,40 @@ namespace Artemis.Core.Services
/// </summary> /// </summary>
public interface IScriptingService : IArtemisService public interface IScriptingService : IArtemisService
{ {
/// <summary>
/// Gets a list of all available scripting providers
/// </summary>
ReadOnlyCollection<ScriptingProvider> ScriptingProviders { get; }
/// <summary> /// <summary>
/// Gets a list of all currently active global scripts /// Gets a list of all currently active global scripts
/// </summary> /// </summary>
ReadOnlyCollection<GlobalScript> GlobalScripts { get; } ReadOnlyCollection<GlobalScript> GlobalScripts { get; }
/// <summary> /// <summary>
/// Creates a <see cref="GlobalScript" /> instance for the given <paramref name="scriptConfiguration" /> /// Adds a script by the provided script configuration to the provided profile and instantiates it.
/// </summary> /// </summary>
/// <param name="scriptConfiguration">The script configuration of the script</param> /// <param name="scriptConfiguration">The script configuration whose script to add.</param>
/// <returns> /// <param name="profile">The profile to add the script to.</param>
/// If the <see cref="ScriptingProvider" /> was found an instance of the script; otherwise <see langword="null" />. ProfileScript AddScript(ScriptConfiguration scriptConfiguration, Profile profile);
/// </returns>
GlobalScript? CreateScriptInstance(ScriptConfiguration scriptConfiguration);
/// <summary> /// <summary>
/// Creates a <see cref="ProfileScript" /> instance for the given <paramref name="scriptConfiguration" /> /// Removes a script by the provided script configuration from the provided profile and disposes it.
/// </summary> /// </summary>
/// <param name="profile">The profile the script configuration is configured for</param> /// <param name="scriptConfiguration">The script configuration whose script to remove.</param>
/// <param name="scriptConfiguration">The script configuration of the script</param> /// <param name="profile">The profile to remove the script from.</param>
/// <returns> void RemoveScript(ScriptConfiguration scriptConfiguration, Profile profile);
/// If the <see cref="ScriptingProvider" /> was found an instance of the script; otherwise <see langword="null" />.
/// </returns>
ProfileScript? CreateScriptInstance(Profile profile, ScriptConfiguration scriptConfiguration);
/// <summary> /// <summary>
/// Deletes the provided global script by it's configuration /// Adds a script by the provided script configuration to the global collection and instantiates it.
/// </summary> /// </summary>
void DeleteScript(ScriptConfiguration scriptConfiguration); /// <param name="scriptConfiguration">The script configuration whose script to add.</param>
GlobalScript AddScript(ScriptConfiguration scriptConfiguration);
/// <summary>
/// Removes a script by the provided script configuration from the global collection and disposes it.
/// </summary>
/// <param name="scriptConfiguration">The script configuration whose script to remove.</param>
void RemoveScript(ScriptConfiguration scriptConfiguration);
} }
} }

View File

@ -6,29 +6,28 @@ using System.Reflection;
using Artemis.Core.ScriptingProviders; using Artemis.Core.ScriptingProviders;
using Ninject; using Ninject;
using Ninject.Parameters; using Ninject.Parameters;
using Serilog;
namespace Artemis.Core.Services namespace Artemis.Core.Services
{ {
internal class ScriptingService : IScriptingService internal class ScriptingService : IScriptingService
{ {
private readonly ILogger _logger;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
private List<ScriptingProvider> _scriptingProviders; private readonly List<GlobalScript> _globalScripts;
private readonly List<ScriptingProvider> _scriptingProviders;
public ScriptingService(ILogger logger, IPluginManagementService pluginManagementService, IProfileService profileService) public ScriptingService(IPluginManagementService pluginManagementService, IProfileService profileService)
{ {
_logger = logger;
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
_profileService = profileService; _profileService = profileService;
InternalGlobalScripts = new List<GlobalScript>();
GlobalScripts = new ReadOnlyCollection<GlobalScript>(InternalGlobalScripts);
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled; _pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled; _pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
_scriptingProviders = _pluginManagementService.GetFeaturesOfType<ScriptingProvider>(); _scriptingProviders = _pluginManagementService.GetFeaturesOfType<ScriptingProvider>();
_globalScripts = new List<GlobalScript>();
ScriptingProviders = new ReadOnlyCollection<ScriptingProvider>(_scriptingProviders);
GlobalScripts = new ReadOnlyCollection<GlobalScript>(_globalScripts);
// No need to sub to Deactivated, scripts will deactivate themselves // No need to sub to Deactivated, scripts will deactivate themselves
profileService.ProfileActivated += ProfileServiceOnProfileActivated; profileService.ProfileActivated += ProfileServiceOnProfileActivated;
@ -40,7 +39,110 @@ namespace Artemis.Core.Services
} }
} }
internal List<GlobalScript> InternalGlobalScripts { get; } /// <inheritdoc />
public ReadOnlyCollection<ScriptingProvider> ScriptingProviders { get; }
/// <inheritdoc />
public ReadOnlyCollection<GlobalScript> GlobalScripts { get; }
/// <inheritdoc />
public ProfileScript AddScript(ScriptConfiguration scriptConfiguration, Profile profile)
{
profile.AddScriptConfiguration(scriptConfiguration);
return CreateScriptInstance(scriptConfiguration, profile);
}
/// <inheritdoc />
public void RemoveScript(ScriptConfiguration scriptConfiguration, Profile profile)
{
profile.RemoveScriptConfiguration(scriptConfiguration);
}
/// <inheritdoc />
public GlobalScript AddScript(ScriptConfiguration scriptConfiguration)
{
throw new NotImplementedException("Global scripts are not yet implemented.");
}
/// <inheritdoc />
public void RemoveScript(ScriptConfiguration scriptConfiguration)
{
throw new NotImplementedException("Global scripts are not yet implemented.");
}
private GlobalScript CreateScriptInstance(ScriptConfiguration scriptConfiguration)
{
GlobalScript? script = null;
try
{
if (scriptConfiguration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
if (provider == null)
throw new ArtemisCoreException($"Can't create script instance as there is no matching scripting provider found for the script ({scriptConfiguration.ScriptingProviderId}).");
script = (GlobalScript) provider.Plugin.Kernel!.Get(
provider.GlobalScriptType,
CreateScriptConstructorArgument(provider.GlobalScriptType, scriptConfiguration)
);
script.ScriptingProvider = provider;
script.ScriptingService = this;
scriptConfiguration.Script = script;
provider.InternalScripts.Add(script);
return script;
}
catch (Exception e)
{
script?.Dispose();
throw new ArtemisCoreException("Failed to initialize global script", e);
}
}
private ProfileScript CreateScriptInstance(ScriptConfiguration scriptConfiguration, Profile profile)
{
ProfileScript? script = null;
try
{
if (scriptConfiguration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
if (provider == null)
throw new ArtemisCoreException($"Can't create script instance as there is no matching scripting provider found for the script ({scriptConfiguration.ScriptingProviderId}).");
script = (ProfileScript) provider.Plugin.Kernel!.Get(
provider.ProfileScriptType,
CreateScriptConstructorArgument(provider.ProfileScriptType, profile),
CreateScriptConstructorArgument(provider.ProfileScriptType, scriptConfiguration)
);
script.ScriptingProvider = provider;
scriptConfiguration.Script = script;
provider.InternalScripts.Add(script);
lock (profile)
{
profile.AddScript(script);
}
return script;
}
catch (Exception e)
{
// If something went wrong but the script was created, clean up as best we can
if (script != null)
{
if (profile.Scripts.Contains(script))
profile.RemoveScript(script);
else
script.Dispose();
}
throw new ArtemisCoreException("Failed to initialize profile script", e);
}
}
private ConstructorArgument CreateScriptConstructorArgument(Type scriptType, object value) private ConstructorArgument CreateScriptConstructorArgument(Type scriptType, object value)
{ {
@ -57,9 +159,19 @@ namespace Artemis.Core.Services
return new ConstructorArgument(configurationParameter.Name, value); return new ConstructorArgument(configurationParameter.Name, value);
} }
private void InitializeProfileScripts(Profile profile)
{
// Initialize the scripts on the profile
foreach (ScriptConfiguration scriptConfiguration in profile.ScriptConfigurations.Where(c => c.Script == null && _scriptingProviders.Any(p => p.Id == c.ScriptingProviderId)))
CreateScriptInstance(scriptConfiguration, profile);
}
#region Event handlers
private void PluginManagementServiceOnPluginFeatureToggled(object? sender, PluginFeatureEventArgs e) private void PluginManagementServiceOnPluginFeatureToggled(object? sender, PluginFeatureEventArgs e)
{ {
_scriptingProviders = _pluginManagementService.GetFeaturesOfType<ScriptingProvider>(); _scriptingProviders.Clear();
_scriptingProviders.AddRange(_pluginManagementService.GetFeaturesOfType<ScriptingProvider>());
foreach (ProfileConfiguration profileConfiguration in _profileService.ProfileConfigurations) foreach (ProfileConfiguration profileConfiguration in _profileService.ProfileConfigurations)
{ {
@ -74,87 +186,6 @@ namespace Artemis.Core.Services
InitializeProfileScripts(e.ProfileConfiguration.Profile); InitializeProfileScripts(e.ProfileConfiguration.Profile);
} }
private void InitializeProfileScripts(Profile profile) #endregion
{
// Initialize the scripts on the profile
foreach (ScriptConfiguration scriptConfiguration in profile.ScriptConfigurations.Where(c => c.Script == null))
CreateScriptInstance(profile, scriptConfiguration);
}
public ReadOnlyCollection<GlobalScript> GlobalScripts { get; }
public GlobalScript? CreateScriptInstance(ScriptConfiguration scriptConfiguration)
{
GlobalScript? script = null;
try
{
if (scriptConfiguration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
if (provider == null)
return null;
script = (GlobalScript) provider.Plugin.Kernel!.Get(
provider.GlobalScriptType,
CreateScriptConstructorArgument(provider.GlobalScriptType, scriptConfiguration)
);
script.ScriptingProvider = provider;
script.ScriptingService = this;
provider.InternalScripts.Add(script);
InternalGlobalScripts.Add(script);
scriptConfiguration.Script = script;
return script;
}
catch (Exception e)
{
_logger.Warning(e, "Failed to initialize global script");
script?.Dispose();
return null;
}
}
public ProfileScript? CreateScriptInstance(Profile profile, ScriptConfiguration scriptConfiguration)
{
ProfileScript? script = null;
try
{
if (scriptConfiguration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
if (provider == null)
return null;
script = (ProfileScript) provider.Plugin.Kernel!.Get(
provider.ProfileScriptType,
CreateScriptConstructorArgument(provider.ProfileScriptType, profile),
CreateScriptConstructorArgument(provider.ProfileScriptType, scriptConfiguration)
);
script.ScriptingProvider = provider;
provider.InternalScripts.Add(script);
lock (profile)
{
scriptConfiguration.Script = script;
profile.Scripts.Add(script);
}
return script;
}
catch (Exception e)
{
_logger.Warning(e, "Failed to initialize profile script");
script?.Dispose();
return null;
}
}
/// <inheritdoc />
public void DeleteScript(ScriptConfiguration scriptConfiguration)
{
}
} }
} }

View File

@ -103,6 +103,16 @@ namespace Artemis.Core.Services
/// <typeparam name="T">The type of Web API controller to remove</typeparam> /// <typeparam name="T">The type of Web API controller to remove</typeparam>
void RemoveController<T>() where T : WebApiController; void RemoveController<T>() where T : WebApiController;
/// <summary>
/// Adds a new EmbedIO module and restarts the web server
/// </summary>
void AddModule(PluginFeature feature, Func<IWebModule> create);
/// <summary>
/// Removes a EmbedIO module and restarts the web server
/// </summary>
void RemoveModule(Func<IWebModule> create);
/// <summary> /// <summary>
/// Adds a new EmbedIO module and restarts the web server /// Adds a new EmbedIO module and restarts the web server
/// </summary> /// </summary>

View File

@ -7,14 +7,28 @@ namespace Artemis.Core.Services
internal class WebModuleRegistration internal class WebModuleRegistration
{ {
public PluginFeature Feature { get; } public PluginFeature Feature { get; }
public Type WebModuleType { get; } public Type? WebModuleType { get; }
public Func<IWebModule>? Create { get; }
public WebModuleRegistration(PluginFeature feature, Type webModuleType) public WebModuleRegistration(PluginFeature feature, Type webModuleType)
{ {
Feature = feature; Feature = feature ?? throw new ArgumentNullException(nameof(feature));
WebModuleType = webModuleType; WebModuleType = webModuleType ?? throw new ArgumentNullException(nameof(webModuleType));
} }
public IWebModule CreateInstance() => (IWebModule) Feature.Plugin.Kernel!.Get(WebModuleType); public WebModuleRegistration(PluginFeature feature, Func<IWebModule> create)
{
Feature = feature ?? throw new ArgumentNullException(nameof(feature));
Create = create ?? throw new ArgumentNullException(nameof(create));
}
public IWebModule CreateInstance()
{
if (Create != null)
return Create();
if (WebModuleType != null)
return (IWebModule) Feature.Plugin.Kernel!.Get(WebModuleType);
throw new ArtemisCoreException("WebModuleRegistration doesn't have a create function nor a web module type :(");
}
} }
} }

View File

@ -170,6 +170,20 @@ namespace Artemis.Core.Services
#region Module management #region Module management
public void AddModule(PluginFeature feature, Func<IWebModule> create)
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
_modules.Add(new WebModuleRegistration(feature, create));
StartWebServer();
}
public void RemoveModule(Func<IWebModule> create)
{
_modules.RemoveAll(r => r.Create == create);
StartWebServer();
}
public void AddModule<T>(PluginFeature feature) where T : IWebModule public void AddModule<T>(PluginFeature feature) where T : IWebModule
{ {
if (feature == null) throw new ArgumentNullException(nameof(feature)); if (feature == null) throw new ArgumentNullException(nameof(feature));

View File

@ -34,7 +34,7 @@ namespace Artemis.UI.Linux
private void RegisterProviders() private void RegisterProviders()
{ {
IInputService inputService = _kernel.Get<IInputService>(); IInputService inputService = _kernel.Get<IInputService>();
inputService.AddInputProvider(_kernel.Get<LinuxInputProvider>()); // inputService.AddInputProvider(_kernel.Get<LinuxInputProvider>());
} }
} }
} }

View File

@ -206,8 +206,8 @@
}, },
"FluentAvaloniaUI": { "FluentAvaloniaUI": {
"type": "Transitive", "type": "Transitive",
"resolved": "1.4.0", "resolved": "1.4.1",
"contentHash": "K0dwenW6dbRFSnJmJAqIVWVlGIakmodgMxXWyj0gm1MoLJkuMJ5vMU/skw5X7xJJDpr88mcB4FkMPjEIp1vk9A==", "contentHash": "2m9e3YuCNa0a7EBHA9HXVq5EeA5/xtNKIJU4utMhUKHHCUgxKnBWffHUbCKzPGhhsVrVnK4Uwb/WyI8nQCHEZw==",
"dependencies": { "dependencies": {
"Avalonia": "0.10.15", "Avalonia": "0.10.15",
"Avalonia.Controls.DataGrid": "0.10.15", "Avalonia.Controls.DataGrid": "0.10.15",
@ -1718,7 +1718,7 @@
"Avalonia.ReactiveUI": "0.10.15", "Avalonia.ReactiveUI": "0.10.15",
"Avalonia.Xaml.Behaviors": "0.10.14", "Avalonia.Xaml.Behaviors": "0.10.14",
"DynamicData": "7.8.6", "DynamicData": "7.8.6",
"FluentAvaloniaUI": "1.4.0", "FluentAvaloniaUI": "1.4.1",
"Flurl.Http": "3.2.4", "Flurl.Http": "3.2.4",
"Live.Avalonia": "1.3.1", "Live.Avalonia": "1.3.1",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
@ -1739,7 +1739,7 @@
"Avalonia.ReactiveUI": "0.10.15", "Avalonia.ReactiveUI": "0.10.15",
"Avalonia.Xaml.Behaviors": "0.10.14", "Avalonia.Xaml.Behaviors": "0.10.14",
"DynamicData": "7.8.6", "DynamicData": "7.8.6",
"FluentAvaloniaUI": "1.4.0", "FluentAvaloniaUI": "1.4.1",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
"RGB.NET.Core": "1.0.0-prerelease.32", "RGB.NET.Core": "1.0.0-prerelease.32",
"ReactiveUI": "17.1.50", "ReactiveUI": "17.1.50",

View File

@ -206,8 +206,8 @@
}, },
"FluentAvaloniaUI": { "FluentAvaloniaUI": {
"type": "Transitive", "type": "Transitive",
"resolved": "1.4.0", "resolved": "1.4.1",
"contentHash": "K0dwenW6dbRFSnJmJAqIVWVlGIakmodgMxXWyj0gm1MoLJkuMJ5vMU/skw5X7xJJDpr88mcB4FkMPjEIp1vk9A==", "contentHash": "2m9e3YuCNa0a7EBHA9HXVq5EeA5/xtNKIJU4utMhUKHHCUgxKnBWffHUbCKzPGhhsVrVnK4Uwb/WyI8nQCHEZw==",
"dependencies": { "dependencies": {
"Avalonia": "0.10.15", "Avalonia": "0.10.15",
"Avalonia.Controls.DataGrid": "0.10.15", "Avalonia.Controls.DataGrid": "0.10.15",
@ -1718,7 +1718,7 @@
"Avalonia.ReactiveUI": "0.10.15", "Avalonia.ReactiveUI": "0.10.15",
"Avalonia.Xaml.Behaviors": "0.10.14", "Avalonia.Xaml.Behaviors": "0.10.14",
"DynamicData": "7.8.6", "DynamicData": "7.8.6",
"FluentAvaloniaUI": "1.4.0", "FluentAvaloniaUI": "1.4.1",
"Flurl.Http": "3.2.4", "Flurl.Http": "3.2.4",
"Live.Avalonia": "1.3.1", "Live.Avalonia": "1.3.1",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
@ -1739,7 +1739,7 @@
"Avalonia.ReactiveUI": "0.10.15", "Avalonia.ReactiveUI": "0.10.15",
"Avalonia.Xaml.Behaviors": "0.10.14", "Avalonia.Xaml.Behaviors": "0.10.14",
"DynamicData": "7.8.6", "DynamicData": "7.8.6",
"FluentAvaloniaUI": "1.4.0", "FluentAvaloniaUI": "1.4.1",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
"RGB.NET.Core": "1.0.0-prerelease.32", "RGB.NET.Core": "1.0.0-prerelease.32",
"ReactiveUI": "17.1.50", "ReactiveUI": "17.1.50",

View File

@ -20,7 +20,7 @@
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" /> <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.14" /> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.14" />
<PackageReference Include="DynamicData" Version="7.8.6" /> <PackageReference Include="DynamicData" Version="7.8.6" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.0" /> <PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" /> <PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
<PackageReference Include="ReactiveUI" Version="17.1.50" /> <PackageReference Include="ReactiveUI" Version="17.1.50" />
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1" /> <PackageReference Include="ReactiveUI.Validation" Version="2.2.1" />

View File

@ -17,7 +17,7 @@ namespace Artemis.UI.Shared.Services.Builders
/// Creates a new instance of the <see cref="OpenFileDialogBuilder"/> class. /// Creates a new instance of the <see cref="OpenFileDialogBuilder"/> class.
/// </summary> /// </summary>
/// <param name="parent">The parent window that will host the dialog.</param> /// <param name="parent">The parent window that will host the dialog.</param>
public OpenFileDialogBuilder(Window parent) internal OpenFileDialogBuilder(Window parent)
{ {
_parent = parent; _parent = parent;
_openFileDialog = new OpenFileDialog(); _openFileDialog = new OpenFileDialog();

View File

@ -17,7 +17,7 @@ namespace Artemis.UI.Shared.Services.Builders
/// Creates a new instance of the <see cref="SaveFileDialogBuilder" /> class. /// Creates a new instance of the <see cref="SaveFileDialogBuilder" /> class.
/// </summary> /// </summary>
/// <param name="parent">The parent window that will host the notification.</param> /// <param name="parent">The parent window that will host the notification.</param>
public SaveFileDialogBuilder(Window parent) internal SaveFileDialogBuilder(Window parent)
{ {
_parent = parent; _parent = parent;
_saveFileDialog = new SaveFileDialog(); _saveFileDialog = new SaveFileDialog();

View File

@ -62,9 +62,9 @@
}, },
"FluentAvaloniaUI": { "FluentAvaloniaUI": {
"type": "Direct", "type": "Direct",
"requested": "[1.4.0, )", "requested": "[1.4.1, )",
"resolved": "1.4.0", "resolved": "1.4.1",
"contentHash": "K0dwenW6dbRFSnJmJAqIVWVlGIakmodgMxXWyj0gm1MoLJkuMJ5vMU/skw5X7xJJDpr88mcB4FkMPjEIp1vk9A==", "contentHash": "2m9e3YuCNa0a7EBHA9HXVq5EeA5/xtNKIJU4utMhUKHHCUgxKnBWffHUbCKzPGhhsVrVnK4Uwb/WyI8nQCHEZw==",
"dependencies": { "dependencies": {
"Avalonia": "0.10.15", "Avalonia": "0.10.15",
"Avalonia.Controls.DataGrid": "0.10.15", "Avalonia.Controls.DataGrid": "0.10.15",

View File

@ -232,8 +232,8 @@
}, },
"FluentAvaloniaUI": { "FluentAvaloniaUI": {
"type": "Transitive", "type": "Transitive",
"resolved": "1.4.0", "resolved": "1.4.1",
"contentHash": "K0dwenW6dbRFSnJmJAqIVWVlGIakmodgMxXWyj0gm1MoLJkuMJ5vMU/skw5X7xJJDpr88mcB4FkMPjEIp1vk9A==", "contentHash": "2m9e3YuCNa0a7EBHA9HXVq5EeA5/xtNKIJU4utMhUKHHCUgxKnBWffHUbCKzPGhhsVrVnK4Uwb/WyI8nQCHEZw==",
"dependencies": { "dependencies": {
"Avalonia": "0.10.15", "Avalonia": "0.10.15",
"Avalonia.Controls.DataGrid": "0.10.15", "Avalonia.Controls.DataGrid": "0.10.15",
@ -1753,7 +1753,7 @@
"Avalonia.ReactiveUI": "0.10.15", "Avalonia.ReactiveUI": "0.10.15",
"Avalonia.Xaml.Behaviors": "0.10.14", "Avalonia.Xaml.Behaviors": "0.10.14",
"DynamicData": "7.8.6", "DynamicData": "7.8.6",
"FluentAvaloniaUI": "1.4.0", "FluentAvaloniaUI": "1.4.1",
"Flurl.Http": "3.2.4", "Flurl.Http": "3.2.4",
"Live.Avalonia": "1.3.1", "Live.Avalonia": "1.3.1",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
@ -1774,7 +1774,7 @@
"Avalonia.ReactiveUI": "0.10.15", "Avalonia.ReactiveUI": "0.10.15",
"Avalonia.Xaml.Behaviors": "0.10.14", "Avalonia.Xaml.Behaviors": "0.10.14",
"DynamicData": "7.8.6", "DynamicData": "7.8.6",
"FluentAvaloniaUI": "1.4.0", "FluentAvaloniaUI": "1.4.1",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
"RGB.NET.Core": "1.0.0-prerelease.32", "RGB.NET.Core": "1.0.0-prerelease.32",
"ReactiveUI": "17.1.50", "ReactiveUI": "17.1.50",

View File

@ -23,7 +23,7 @@
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" /> <PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.14" /> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.14" />
<PackageReference Include="DynamicData" Version="7.8.6" /> <PackageReference Include="DynamicData" Version="7.8.6" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.0" /> <PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />
<PackageReference Include="Flurl.Http" Version="3.2.4" /> <PackageReference Include="Flurl.Http" Version="3.2.4" />
<PackageReference Include="Live.Avalonia" Version="1.3.1" /> <PackageReference Include="Live.Avalonia" Version="1.3.1" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" /> <PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
@ -71,6 +71,10 @@
<DependentUpon>SidebarCategoryEditView.axaml</DependentUpon> <DependentUpon>SidebarCategoryEditView.axaml</DependentUpon>
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Update="Screens\Scripting\Dialogs\ScriptConfigurationEditView.axaml.cs">
<DependentUpon>ScriptConfigurationEditView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AvaloniaXaml Update="DefaultTypes\PropertyInput\StringPropertyInputView.axaml"> <AvaloniaXaml Update="DefaultTypes\PropertyInput\StringPropertyInputView.axaml">

View File

@ -2,6 +2,7 @@
using Artemis.Core; using Artemis.Core;
using Artemis.Core.LayerBrushes; using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects; using Artemis.Core.LayerEffects;
using Artemis.Core.ScriptingProviders;
using Artemis.UI.Screens.Device; using Artemis.UI.Screens.Device;
using Artemis.UI.Screens.Plugins; using Artemis.UI.Screens.Plugins;
using Artemis.UI.Screens.ProfileEditor; using Artemis.UI.Screens.ProfileEditor;
@ -13,6 +14,8 @@ using Artemis.UI.Screens.ProfileEditor.Properties.DataBinding;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline; using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
using Artemis.UI.Screens.ProfileEditor.Properties.Tree; using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers; using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
using Artemis.UI.Screens.Scripting;
using Artemis.UI.Screens.Scripting.Dialogs;
using Artemis.UI.Screens.Settings; using Artemis.UI.Screens.Settings;
using Artemis.UI.Screens.Sidebar; using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Screens.SurfaceEditor; using Artemis.UI.Screens.SurfaceEditor;
@ -123,3 +126,9 @@ public interface ILayerHintVmFactory : IVmFactory
DeviceAdaptionHintViewModel DeviceAdaptionHintViewModel(Layer layer, DeviceAdaptionHint adaptionHint); DeviceAdaptionHintViewModel DeviceAdaptionHintViewModel(Layer layer, DeviceAdaptionHint adaptionHint);
KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(Layer layer, KeyboardSectionAdaptionHint adaptionHint); KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(Layer layer, KeyboardSectionAdaptionHint adaptionHint);
} }
public interface IScriptVmFactory : IVmFactory
{
ScriptConfigurationViewModel ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration);
ScriptConfigurationViewModel ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration);
}

View File

@ -0,0 +1,84 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisitesInstallDialogView"
x:DataType="plugins:PluginPrerequisitesInstallDialogViewModel">
<UserControl.Styles>
<Styles>
<Style Selector="Border.status-border">
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" />
<Setter Property="CornerRadius" Value="16" />
</Style>
<Style Selector="Border.status-border avalonia|MaterialIcon">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</Styles>
</UserControl.Styles>
<Grid ColumnDefinitions="250,*">
<Grid.RowDefinitions>
<RowDefinition MinHeight="200" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
Grid.Column="0"
Items="{CompiledBinding Prerequisites}"
SelectedItem="{CompiledBinding ActivePrerequisite, Mode=OneWay}"
IsHitTestVisible="False">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type plugins:PluginPrerequisiteViewModel}">
<Grid ColumnDefinitions="Auto,*" Margin="0 6">
<Border Grid.Row="0" Grid.Column="0" Classes="status-border" IsVisible="{CompiledBinding !IsMet}" Background="#ff3838">
<avalonia:MaterialIcon Kind="Close" />
</Border>
<Border Grid.Row="0" Grid.Column="0" Classes="status-border" IsVisible="{CompiledBinding IsMet}" Background="#32a852">
<avalonia:MaterialIcon Kind="Check" />
</Border>
<StackPanel Margin="8 0 0 0" Grid.Column="1" VerticalAlignment="Stretch">
<TextBlock FontWeight="Bold" Text="{CompiledBinding PluginPrerequisite.Name}" TextWrapping="Wrap" />
<TextBlock Text="{CompiledBinding PluginPrerequisite.Description}" TextWrapping="Wrap" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Row="0"
Grid.Column="1"
Content="{CompiledBinding ActivePrerequisite}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Margin="10 0"
IsTabStop="False"
IsVisible="{CompiledBinding ShowProgress, Mode=OneWay}" />
<TextBlock Grid.Row="0"
Grid.Column="1"
TextWrapping="Wrap"
Margin="10 0"
IsVisible="{CompiledBinding ShowIntro, Mode=OneWay}">
In order for this plugin to work the prerequisites on the left must be met. Clicking install will automatically set everything up for you.
</TextBlock>
<StackPanel Grid.Row="0"
Grid.Column="1"
Margin="10 0"
IsVisible="{CompiledBinding ShowFailed, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<TextBlock>Installing</TextBlock>
<TextBlock Text="{CompiledBinding ActivePrerequisite.PluginPrerequisite.Name, Mode=OneWay}" FontWeight="SemiBold" TextWrapping="Wrap" />
<TextBlock>failed.</TextBlock>
</StackPanel>
<TextBlock TextWrapping="Wrap">You may try again to see if that helps, otherwise install the prerequisite manually or contact the plugin developer.</TextBlock>
</StackPanel>
</Grid>
</UserControl>

View File

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginPrerequisitesInstallDialogView : ReactiveUserControl<PluginPrerequisitesInstallDialogViewModel>
{
public PluginPrerequisitesInstallDialogView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Reactive;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -9,11 +10,14 @@ using Artemis.Core;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using ReactiveUI; using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Plugins namespace Artemis.UI.Screens.Plugins
{ {
public class PluginPrerequisitesInstallDialogViewModel : DialogViewModelBase<bool> public class PluginPrerequisitesInstallDialogViewModel : ContentDialogViewModelBase
{ {
private PluginPrerequisiteViewModel? _activePrerequisite; private PluginPrerequisiteViewModel? _activePrerequisite;
private bool _canInstall; private bool _canInstall;
@ -26,22 +30,23 @@ namespace Artemis.UI.Screens.Plugins
public PluginPrerequisitesInstallDialogViewModel(List<IPrerequisitesSubject> subjects, IPrerequisitesVmFactory prerequisitesVmFactory) public PluginPrerequisitesInstallDialogViewModel(List<IPrerequisitesSubject> subjects, IPrerequisitesVmFactory prerequisitesVmFactory)
{ {
Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>(); Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>();
foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.Prerequisites)) foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.PlatformPrerequisites))
Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, false)); Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, false));
Install = ReactiveCommand.CreateFromTask(ExecuteInstall, this.WhenAnyValue(vm => vm.CanInstall));
CanInstall = false; Dispatcher.UIThread.Post(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet()), DispatcherPriority.Background);
Task.Run(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet()));
this.WhenActivated(d => this.WhenActivated(d =>
{ {
Disposable.Create(() => Disposable.Create(() =>
{ {
_tokenSource?.Cancel(); _tokenSource?.Cancel();
_tokenSource?.Dispose(); _tokenSource?.Dispose();
_tokenSource = null;
}).DisposeWith(d); }).DisposeWith(d);
}); });
} }
public ReactiveCommand<Unit, Unit> Install { get; }
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; } public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel? ActivePrerequisite public PluginPrerequisiteViewModel? ActivePrerequisite
@ -80,13 +85,18 @@ namespace Artemis.UI.Screens.Plugins
set => RaiseAndSetIfChanged(ref _canInstall, value); set => RaiseAndSetIfChanged(ref _canInstall, value);
} }
public async Task Install() private async Task ExecuteInstall()
{ {
ContentDialogClosingDeferral? deferral = null;
if (ContentDialog != null)
ContentDialog.Closing += (_, args) => deferral = args.GetDeferral();
CanInstall = false; CanInstall = false;
ShowFailed = false; ShowFailed = false;
ShowIntro = false; ShowIntro = false;
ShowProgress = true; ShowProgress = true;
_tokenSource?.Dispose();
_tokenSource = new CancellationTokenSource(); _tokenSource = new CancellationTokenSource();
try try
@ -110,30 +120,31 @@ namespace Artemis.UI.Screens.Plugins
// Wait after the task finished for the user to process what happened // Wait after the task finished for the user to process what happened
if (pluginPrerequisiteViewModel != Prerequisites.Last()) if (pluginPrerequisiteViewModel != Prerequisites.Last())
await Task.Delay(250);
else
await Task.Delay(1000); await Task.Delay(1000);
} }
ShowInstall = false; if (deferral != null)
deferral.Complete();
else
ContentDialog?.Hide(ContentDialogResult.Primary);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
// ignored // ignored
} }
finally
{
_tokenSource.Dispose();
_tokenSource = null;
}
} }
public void Accept() public static async Task Show(IWindowService windowService, List<IPrerequisitesSubject> subjects)
{ {
Close(true); await windowService.CreateContentDialog()
} .WithTitle("Plugin prerequisites")
.WithViewModel(out PluginPrerequisitesInstallDialogViewModel vm, ("subjects", subjects))
public static async Task<bool> Show(IWindowService windowService, List<IPrerequisitesSubject> subjects) .WithCloseButtonText("Cancel")
{ .HavingPrimaryButton(b => b.WithText("Install").WithCommand(vm.Install))
return await windowService.ShowDialogAsync<PluginPrerequisitesInstallDialogViewModel, bool>(("subjects", subjects)); .WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
} }
} }
} }

View File

@ -0,0 +1,72 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisitesUninstallDialogView"
x:DataType="plugins:PluginPrerequisitesUninstallDialogViewModel">
<UserControl.Styles>
<Styles>
<Style Selector="Border.status-border">
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" />
<Setter Property="CornerRadius" Value="16" />
</Style>
<Style Selector="Border.status-border avalonia|MaterialIcon">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</Styles>
</UserControl.Styles>
<Grid ColumnDefinitions="250,*">
<Grid.RowDefinitions>
<RowDefinition MinHeight="200" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
Grid.Column="0"
Items="{CompiledBinding Prerequisites}"
SelectedItem="{CompiledBinding ActivePrerequisite, Mode=OneWay}"
IsHitTestVisible="False">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type plugins:PluginPrerequisiteViewModel}">
<Grid ColumnDefinitions="Auto,*" Margin="0 6">
<Border Grid.Row="0" Grid.Column="0" Classes="status-border" IsVisible="{CompiledBinding !IsMet}" Background="#ff3838">
<avalonia:MaterialIcon Kind="Close" />
</Border>
<Border Grid.Row="0" Grid.Column="0" Classes="status-border" IsVisible="{CompiledBinding IsMet}" Background="#32a852">
<avalonia:MaterialIcon Kind="Check" />
</Border>
<StackPanel Margin="8 0 0 0" Grid.Column="1" VerticalAlignment="Stretch">
<TextBlock FontWeight="Bold" Text="{CompiledBinding PluginPrerequisite.Name}" TextWrapping="Wrap" />
<TextBlock Text="{CompiledBinding PluginPrerequisite.Description}" TextWrapping="Wrap" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Row="0"
Grid.Column="1"
Content="{CompiledBinding ActivePrerequisite}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Margin="10 0"
IsTabStop="False"
IsVisible="{CompiledBinding ActivePrerequisite, Converter={x:Static ObjectConverters.IsNotNull}}" />
<TextBlock Grid.Row="0"
Grid.Column="1"
TextWrapping="Wrap"
Margin="10 0"
IsVisible="{CompiledBinding ActivePrerequisite, Converter={x:Static ObjectConverters.IsNull}}">
This plugin/feature installed certain prerequisites in order to function. In this screen you can chose to remove these, this will mean the plugin/feature will no longer work until you reinstall the prerequisites.
</TextBlock>
</Grid>
</UserControl>

View File

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginPrerequisitesUninstallDialogView : ReactiveUserControl<PluginPrerequisitesUninstallDialogViewModel>
{
public PluginPrerequisitesUninstallDialogView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Reactive;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -10,34 +11,38 @@ using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using ReactiveUI; using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Plugins namespace Artemis.UI.Screens.Plugins
{ {
public class PluginPrerequisitesUninstallDialogViewModel : DialogViewModelBase<bool> public class PluginPrerequisitesUninstallDialogViewModel : ContentDialogViewModelBase
{ {
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly List<IPrerequisitesSubject> _subjects; private readonly List<IPrerequisitesSubject> _subjects;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private PluginPrerequisiteViewModel? _activePrerequisite; private PluginPrerequisiteViewModel? _activePrerequisite;
private bool _canUninstall; private bool _canUninstall;
private bool _isFinished;
private CancellationTokenSource? _tokenSource; private CancellationTokenSource? _tokenSource;
public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects, string cancelLabel, IPrerequisitesVmFactory prerequisitesVmFactory, IWindowService windowService, public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects,
IPrerequisitesVmFactory prerequisitesVmFactory,
IWindowService windowService,
IPluginManagementService pluginManagementService) IPluginManagementService pluginManagementService)
{ {
_subjects = subjects; _subjects = subjects;
_windowService = windowService; _windowService = windowService;
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
CancelLabel = cancelLabel;
Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>(); Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>();
foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.Prerequisites)) foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.PlatformPrerequisites))
Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, true)); Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, true));
Uninstall = ReactiveCommand.CreateFromTask(ExecuteUninstall, this.WhenAnyValue(vm => vm.CanUninstall));
// Could be slow so take it off of the UI thread // Could be slow so take it off of the UI thread
Task.Run(() => CanUninstall = Prerequisites.Any(p => p.PluginPrerequisite.IsMet())); Dispatcher.UIThread.Post(() => CanUninstall = Prerequisites.Any(p => p.PluginPrerequisite.IsMet()), DispatcherPriority.Background);
this.WhenActivated(d => this.WhenActivated(d =>
{ {
@ -45,11 +50,12 @@ namespace Artemis.UI.Screens.Plugins
{ {
_tokenSource?.Cancel(); _tokenSource?.Cancel();
_tokenSource?.Dispose(); _tokenSource?.Dispose();
_tokenSource = null;
}).DisposeWith(d); }).DisposeWith(d);
}); });
} }
public string CancelLabel { get; } public ReactiveCommand<Unit, Unit> Uninstall { get; }
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; } public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel? ActivePrerequisite public PluginPrerequisiteViewModel? ActivePrerequisite
@ -64,20 +70,19 @@ namespace Artemis.UI.Screens.Plugins
set => RaiseAndSetIfChanged(ref _canUninstall, value); set => RaiseAndSetIfChanged(ref _canUninstall, value);
} }
public bool IsFinished private async Task ExecuteUninstall()
{ {
get => _isFinished; ContentDialogClosingDeferral? deferral = null;
set => RaiseAndSetIfChanged(ref _isFinished, value); if (ContentDialog != null)
} ContentDialog.Closing += (_, args) => deferral = args.GetDeferral();
public async Task Uninstall()
{
CanUninstall = false; CanUninstall = false;
// Disable all subjects that are plugins, this will disable their features too // Disable all subjects that are plugins, this will disable their features too
foreach (IPrerequisitesSubject prerequisitesSubject in _subjects) foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
{ {
if (prerequisitesSubject is PluginInfo pluginInfo) _pluginManagementService.DisablePlugin(pluginInfo.Plugin, true); if (prerequisitesSubject is PluginInfo pluginInfo)
_pluginManagementService.DisablePlugin(pluginInfo.Plugin, true);
} }
// Disable all subjects that are features if still required // Disable all subjects that are features if still required
@ -88,9 +93,11 @@ namespace Artemis.UI.Screens.Plugins
// Disable the parent plugin if the feature is AlwaysEnabled // Disable the parent plugin if the feature is AlwaysEnabled
if (featureInfo.AlwaysEnabled) if (featureInfo.AlwaysEnabled)
_pluginManagementService.DisablePlugin(featureInfo.Plugin, true); _pluginManagementService.DisablePlugin(featureInfo.Plugin, true);
else if (featureInfo.Instance != null) _pluginManagementService.DisablePluginFeature(featureInfo.Instance, true); else if (featureInfo.Instance != null)
_pluginManagementService.DisablePluginFeature(featureInfo.Instance, true);
} }
_tokenSource?.Dispose();
_tokenSource = new CancellationTokenSource(); _tokenSource = new CancellationTokenSource();
try try
@ -98,30 +105,35 @@ namespace Artemis.UI.Screens.Plugins
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites) foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
{ {
pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet(); pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet();
if (!pluginPrerequisiteViewModel.IsMet) continue; if (!pluginPrerequisiteViewModel.IsMet)
continue;
ActivePrerequisite = pluginPrerequisiteViewModel; ActivePrerequisite = pluginPrerequisiteViewModel;
await ActivePrerequisite.Uninstall(_tokenSource.Token); await ActivePrerequisite.Uninstall(_tokenSource.Token);
// Wait after the task finished for the user to process what happened // Wait after the task finished for the user to process what happened
if (pluginPrerequisiteViewModel != Prerequisites.Last()) await Task.Delay(1000); if (pluginPrerequisiteViewModel != Prerequisites.Last())
await Task.Delay(250);
else
await Task.Delay(1000);
} }
if (Prerequisites.All(p => !p.IsMet)) if (deferral != null)
{ deferral.Complete();
IsFinished = true; else
return; ContentDialog?.Hide(ContentDialogResult.Primary);
}
// This shouldn't be happening and the experience isn't very nice for the user (too lazy to make a nice UI for such an edge case) // This shouldn't be happening and the experience isn't very nice for the user (too lazy to make a nice UI for such an edge case)
// but at least give some feedback // but at least give some feedback
Close(false); if (Prerequisites.Any(p => p.IsMet))
{
await _windowService.CreateContentDialog() await _windowService.CreateContentDialog()
.WithTitle("Plugin prerequisites") .WithTitle("Plugin prerequisites")
.WithContent("The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.") .WithContent("The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.")
.ShowAsync(); .ShowAsync();
await Show(_windowService, _subjects); await Show(_windowService, _subjects);
} }
}
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
// ignored // ignored
@ -129,19 +141,18 @@ namespace Artemis.UI.Screens.Plugins
finally finally
{ {
CanUninstall = true; CanUninstall = true;
_tokenSource.Dispose();
_tokenSource = null;
} }
} }
public void Accept() public static async Task Show(IWindowService windowService, List<IPrerequisitesSubject> subjects, string cancelLabel = "Cancel")
{ {
Close(true); await windowService.CreateContentDialog()
} .WithTitle("Plugin prerequisites")
.WithViewModel(out PluginPrerequisitesUninstallDialogViewModel vm, ("subjects", subjects))
public static async Task<object> Show(IWindowService windowService, List<IPrerequisitesSubject> subjects, string cancelLabel = "Cancel") .WithCloseButtonText(cancelLabel)
{ .HavingPrimaryButton(b => b.WithText("Uninstall").WithCommand(vm.Uninstall))
return await windowService.ShowDialogAsync<PluginPrerequisitesUninstallDialogViewModel, bool>(("subjects", subjects), ("cancelLabel", cancelLabel)); .WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
} }
} }
} }

View File

@ -76,8 +76,8 @@ namespace Artemis.UI.Screens.Plugins
} }
public bool CanToggleEnabled => FeatureInfo.Plugin.IsEnabled && !FeatureInfo.AlwaysEnabled; public bool CanToggleEnabled => FeatureInfo.Plugin.IsEnabled && !FeatureInfo.AlwaysEnabled;
public bool CanInstallPrerequisites => FeatureInfo.Prerequisites.Any(); public bool CanInstallPrerequisites => FeatureInfo.PlatformPrerequisites.Any();
public bool CanRemovePrerequisites => FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any()); public bool CanRemovePrerequisites => FeatureInfo.PlatformPrerequisites.Any(p => p.UninstallActions.Any());
public bool IsPopupEnabled => CanInstallPrerequisites || CanRemovePrerequisites; public bool IsPopupEnabled => CanInstallPrerequisites || CanRemovePrerequisites;
public void ShowLogsFolder() public void ShowLogsFolder()
@ -100,13 +100,13 @@ namespace Artemis.UI.Screens.Plugins
public async Task InstallPrerequisites() public async Task InstallPrerequisites()
{ {
if (FeatureInfo.Prerequisites.Any()) if (FeatureInfo.PlatformPrerequisites.Any())
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo}); await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
} }
public async Task RemovePrerequisites() public async Task RemovePrerequisites()
{ {
if (FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any())) if (FeatureInfo.PlatformPrerequisites.Any(p => p.UninstallActions.Any()))
{ {
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo}); await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
this.RaisePropertyChanged(nameof(IsEnabled)); this.RaisePropertyChanged(nameof(IsEnabled));

View File

@ -0,0 +1,21 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisiteActionView"
x:DataType="plugins:PluginPrerequisiteActionViewModel">
<StackPanel>
<ProgressBar Value="{CompiledBinding Action.Progress.Percentage, Mode=OneWay}"
IsIndeterminate="{CompiledBinding Action.ProgressIndeterminate, Mode=OneWay}"
IsVisible="{CompiledBinding Action.ShowProgressBar, Mode=OneWay}"
Margin="0 10"/>
<ProgressBar Value="{CompiledBinding Action.SubProgress.Percentage, Mode=OneWay}"
IsIndeterminate="{CompiledBinding Action.SubProgressIndeterminate, Mode=OneWay}"
IsVisible="{CompiledBinding Action.ShowSubProgressBar, Mode=OneWay}"
Margin="0 10"/>
<TextBlock TextWrapping="Wrap" Text="{CompiledBinding Action.Status, Mode=OneWay}" />
</StackPanel>
</UserControl>

View File

@ -0,0 +1,18 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginPrerequisiteActionView : UserControl
{
public PluginPrerequisiteActionView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,27 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisiteView"
x:DataType="plugins:PluginPrerequisiteViewModel">
<StackPanel>
<TextBlock TextWrapping="Wrap" Text="{CompiledBinding PluginPrerequisite.Name, Mode=OneWay}" />
<TextBlock Classes="subtitle" TextWrapping="Wrap" Text="{CompiledBinding PluginPrerequisite.Description, Mode=OneWay}" Margin="0 0 0 15" />
<StackPanel Orientation="Horizontal" IsVisible="{CompiledBinding ActiveAction, Converter={x:Static ObjectConverters.IsNotNull}}">
<TextBlock Text="Step " />
<TextBlock Text="{CompiledBinding ActiveStepNumber, Mode=OneWay}" />
<TextBlock Text="/" />
<TextBlock Text="{CompiledBinding Actions.Count, Mode=OneWay}" />
<TextBlock Text=" - " />
<TextBlock Text="{CompiledBinding ActiveAction.Action.Name, Mode=OneWay, FallbackValue=''}" />
</StackPanel>
<TextBlock Foreground="{DynamicResource MaterialDesignBodyLight}" TextWrapping="Wrap" IsVisible="{CompiledBinding Actions.Count}">
</TextBlock>
<TextBlock Classes="h4" TextWrapping="Wrap" Text="{CompiledBinding ActiveAction.Action.Name, Mode=OneWay}" IsVisible="{CompiledBinding !Actions.Count}" />
<ContentControl Content="{CompiledBinding ActiveAction}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
</StackPanel>
</UserControl>

View File

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Plugins;
public partial class PluginPrerequisiteView : ReactiveUserControl<PluginPrerequisiteViewModel>
{
public PluginPrerequisiteView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -75,7 +75,6 @@ namespace Artemis.UI.Screens.Plugins
public bool Busy => _busy.Value; public bool Busy => _busy.Value;
public int ActiveStepNumber => _activeStepNumber.Value; public int ActiveStepNumber => _activeStepNumber.Value;
public bool HasMultipleActions => Actions.Count > 1;
public async Task Install(CancellationToken cancellationToken) public async Task Install(CancellationToken cancellationToken)
{ {

View File

@ -5,12 +5,14 @@
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginSettingsView"> x:Class="Artemis.UI.Screens.Plugins.PluginSettingsView"
x:DataType="plugins:PluginSettingsViewModel">
<Border Classes="card" Padding="15" Margin="0 5"> <Border Classes="card" Padding="15" Margin="0 5">
<Grid RowDefinitions="*,Auto" ColumnDefinitions="4*,5*"> <Grid RowDefinitions="*,Auto" ColumnDefinitions="4*,5*">
<Grid Grid.Row="0" RowDefinitions="Auto,Auto,*" ColumnDefinitions="80,*"> <Grid Grid.Row="0" RowDefinitions="Auto,Auto,*" ColumnDefinitions="80,*">
<shared:ArtemisIcon Icon="{Binding Plugin.Info.ResolvedIcon}" <shared:ArtemisIcon Icon="{CompiledBinding Plugin.Info.ResolvedIcon}"
Width="48" Width="48"
Height="48" Height="48"
Margin="0 5 0 0" Margin="0 5 0 0"
@ -18,52 +20,52 @@
Grid.RowSpan="3" Grid.RowSpan="3"
VerticalAlignment="Top" /> VerticalAlignment="Top" />
<TextBlock Grid.Column="1" Grid.Row="0" Classes="h5 no-margin" Text="{Binding Plugin.Info.Name}" /> <TextBlock Grid.Column="1" Grid.Row="0" Classes="h5 no-margin" Text="{CompiledBinding Plugin.Info.Name}" />
<TextBlock Grid.Column="1" <TextBlock Grid.Column="1"
Grid.Row="1" Grid.Row="1"
Classes="subtitle" Classes="subtitle"
Text="{Binding Plugin.Info.Author}" Text="{CompiledBinding Plugin.Info.Author}"
IsVisible="{Binding Plugin.Info.Author, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" /> IsVisible="{CompiledBinding Plugin.Info.Author, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<TextBlock Grid.Column="1" <TextBlock Grid.Column="1"
Grid.Row="2" Grid.Row="2"
TextWrapping="Wrap" TextWrapping="Wrap"
Margin="0 5" Margin="0 5"
Text="{Binding Plugin.Info.Description}" /> Text="{CompiledBinding Plugin.Info.Description}" />
</Grid> </Grid>
<Grid Grid.Row="1" Grid.Column="0" ColumnDefinitions="*,Auto"> <Grid Grid.Row="1" Grid.Column="0" ColumnDefinitions="*,Auto">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<controls:SplitButton Content="Settings" Command="{Binding OpenSettings}"> <controls:SplitButton Content="Settings" Command="{CompiledBinding OpenSettings}">
<controls:SplitButton.Flyout> <controls:SplitButton.Flyout>
<MenuFlyout Placement="Bottom"> <MenuFlyout Placement="Bottom" IsOpen="{CompiledBinding IsSettingsPopupOpen, Mode=OneWayToSource}">
<MenuItem Header="Open plugin directory" Command="{Binding OpenPluginDirectory}"> <MenuItem Header="Open plugin directory" Command="{CompiledBinding OpenPluginDirectory}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="FolderOpen" /> <avalonia:MaterialIcon Kind="FolderOpen" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Reload plugin" Command="{Binding Reload}"> <MenuItem Header="Reload plugin" Command="{CompiledBinding Reload}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Reload" /> <avalonia:MaterialIcon Kind="Reload" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Install prerequisites" Command="{Binding InstallPrerequisites}"> <MenuItem Header="Install prerequisites" Command="{CompiledBinding InstallPrerequisites}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="CheckAll" /> <avalonia:MaterialIcon Kind="CheckAll" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Remove prerequisites" Command="{Binding RemovePrerequisites}"> <MenuItem Header="Remove prerequisites" Command="{CompiledBinding RemovePrerequisites}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="Delete" /> <avalonia:MaterialIcon Kind="Delete" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Clear plugin settings" Command="{Binding RemoveSettings}"> <MenuItem Header="Clear plugin settings" Command="{CompiledBinding RemoveSettings}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="DatabaseRemove" /> <avalonia:MaterialIcon Kind="DatabaseRemove" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="Remove plugin" Command="{Binding Remove}"> <MenuItem Header="Remove plugin" Command="{CompiledBinding Remove}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="DeleteForever" /> <avalonia:MaterialIcon Kind="DeleteForever" />
</MenuItem.Icon> </MenuItem.Icon>
@ -74,14 +76,14 @@
<controls:HyperlinkButton Classes="icon-button icon-button-large" <controls:HyperlinkButton Classes="icon-button icon-button-large"
Margin="5 0" Margin="5 0"
IsVisible="{Binding Plugin.Info.Website, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" IsVisible="{CompiledBinding Plugin.Info.Website, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
NavigateUri="{Binding Plugin.Info.Website}"> NavigateUri="{CompiledBinding Plugin.Info.Website}">
<avalonia:MaterialIcon Kind="Web" /> <avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button icon-button-large" <controls:HyperlinkButton Classes="icon-button icon-button-large"
Margin="5 0" Margin="5 0"
IsVisible="{Binding Plugin.Info.Repository, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" IsVisible="{CompiledBinding Plugin.Info.Repository, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
NavigateUri="{Binding Plugin.Info.Repository}"> NavigateUri="{CompiledBinding Plugin.Info.Repository}">
<avalonia:MaterialIcon Kind="Git" /> <avalonia:MaterialIcon Kind="Git" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
</StackPanel> </StackPanel>
@ -89,28 +91,28 @@
<CheckBox Grid.Row="0" <CheckBox Grid.Row="0"
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Right" HorizontalAlignment="Right"
IsVisible="{Binding !Enabling}" IsVisible="{CompiledBinding !Enabling}"
IsChecked="{Binding IsEnabled}"> IsChecked="{CompiledBinding IsEnabled}">
<StackPanel x:Name="EnableText" Orientation="Horizontal"> <StackPanel x:Name="EnableText" Orientation="Horizontal">
<TextBlock>Plugin enabled</TextBlock> <TextBlock>Plugin enabled</TextBlock>
<avalonia:MaterialIcon Kind="ShieldHalfFull" <avalonia:MaterialIcon Kind="ShieldHalfFull"
Margin="5 0 0 0" Margin="5 0 0 0"
ToolTip.Tip="Plugin requires admin rights" ToolTip.Tip="Plugin requires admin rights"
IsVisible="{Binding Plugin.Info.RequiresAdmin}" /> IsVisible="{CompiledBinding Plugin.Info.RequiresAdmin}" />
</StackPanel> </StackPanel>
</CheckBox> </CheckBox>
<ProgressBar Grid.Row="0" <ProgressBar Grid.Row="0"
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Right" HorizontalAlignment="Right"
IsVisible="{Binding Enabling}" IsVisible="{CompiledBinding Enabling}"
IsIndeterminate="True" /> IsIndeterminate="True" />
</Grid> </Grid>
<Border Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0"> <Border Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0">
<Grid RowDefinitions="Auto,*"> <Grid RowDefinitions="Auto,*">
<TextBlock Classes="h5">Plugin features</TextBlock> <TextBlock Classes="h5">Plugin features</TextBlock>
<ListBox Grid.Row="1" MaxHeight="135" Items="{Binding PluginFeatures}" /> <ListBox Grid.Row="1" MaxHeight="135" Items="{CompiledBinding PluginFeatures}" />
</Grid> </Grid>
</Border> </Border>
</Grid> </Grid>

View File

@ -50,11 +50,14 @@ namespace Artemis.UI.Screens.Plugins
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features) foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false)); PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
Reload = ReactiveCommand.CreateFromTask(ExecuteReload);
OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(x => x.IsEnabled).Select(isEnabled => isEnabled && Plugin.ConfigurationDialog != null)); OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(x => x.IsEnabled).Select(isEnabled => isEnabled && Plugin.ConfigurationDialog != null));
RemoveSettings = ReactiveCommand.CreateFromTask(ExecuteRemoveSettings);
Remove = ReactiveCommand.CreateFromTask(ExecuteRemove);
InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites)); InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites));
RemovePrerequisites = ReactiveCommand.CreateFromTask<bool>(ExecuteRemovePrerequisites, this.WhenAnyValue(x => x.CanRemovePrerequisites)); RemovePrerequisites = ReactiveCommand.CreateFromTask<bool>(ExecuteRemovePrerequisites, this.WhenAnyValue(x => x.CanRemovePrerequisites));
ShowLogsFolder = ReactiveCommand.Create(ExecuteShowLogsFolder);
OpenPluginDirectory = ReactiveCommand.Create(ExecuteOpenPluginDirectory);
this.WhenActivated(d => this.WhenActivated(d =>
{ {
@ -69,9 +72,14 @@ namespace Artemis.UI.Screens.Plugins
}); });
} }
public ReactiveCommand<Unit,Unit> Reload { get; }
public ReactiveCommand<Unit, Unit> OpenSettings { get; } public ReactiveCommand<Unit, Unit> OpenSettings { get; }
public ReactiveCommand<Unit,Unit> RemoveSettings { get; }
public ReactiveCommand<Unit,Unit> Remove { get;}
public ReactiveCommand<Unit, Unit> InstallPrerequisites { get; } public ReactiveCommand<Unit, Unit> InstallPrerequisites { get; }
public ReactiveCommand<bool, Unit> RemovePrerequisites { get; } public ReactiveCommand<bool, Unit> RemovePrerequisites { get; }
public ReactiveCommand<Unit,Unit> ShowLogsFolder { get; }
public ReactiveCommand<Unit,Unit> OpenPluginDirectory { get; }
public ObservableCollection<PluginFeatureViewModel> PluginFeatures { get; } public ObservableCollection<PluginFeatureViewModel> PluginFeatures { get; }
@ -137,7 +145,7 @@ namespace Artemis.UI.Screens.Plugins
} }
} }
public void OpenPluginDirectory() private void ExecuteOpenPluginDirectory()
{ {
try try
{ {
@ -149,7 +157,7 @@ namespace Artemis.UI.Screens.Plugins
} }
} }
public async Task Reload() private async Task ExecuteReload()
{ {
bool wasEnabled = IsEnabled; bool wasEnabled = IsEnabled;
@ -167,28 +175,28 @@ namespace Artemis.UI.Screens.Plugins
_notificationService.CreateNotification().WithTitle("Reloaded plugin.").Show(); _notificationService.CreateNotification().WithTitle("Reloaded plugin.").Show();
} }
public async Task ExecuteInstallPrerequisites() private async Task ExecuteInstallPrerequisites()
{ {
List<IPrerequisitesSubject> subjects = new() {Plugin.Info}; List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled)); subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled));
if (subjects.Any(s => s.Prerequisites.Any())) if (subjects.Any(s => s.PlatformPrerequisites.Any()))
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects); await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
} }
public async Task ExecuteRemovePrerequisites(bool forPluginRemoval = false) private async Task ExecuteRemovePrerequisites(bool forPluginRemoval = false)
{ {
List<IPrerequisitesSubject> subjects = new() {Plugin.Info}; List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(!forPluginRemoval ? Plugin.Features.Where(f => f.AlwaysEnabled) : Plugin.Features); subjects.AddRange(!forPluginRemoval ? Plugin.Features.Where(f => f.AlwaysEnabled) : Plugin.Features);
if (subjects.Any(s => s.Prerequisites.Any(p => p.UninstallActions.Any()))) if (subjects.Any(s => s.PlatformPrerequisites.Any(p => p.UninstallActions.Any())))
{ {
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, subjects, forPluginRemoval ? "Skip, remove plugin" : "Cancel"); await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, subjects, forPluginRemoval ? "Skip, remove plugin" : "Cancel");
this.RaisePropertyChanged(nameof(IsEnabled)); this.RaisePropertyChanged(nameof(IsEnabled));
} }
} }
public async Task RemoveSettings() private async Task ExecuteRemoveSettings()
{ {
bool confirmed = await _windowService.ShowConfirmContentDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?"); bool confirmed = await _windowService.ShowConfirmContentDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?");
if (!confirmed) if (!confirmed)
@ -207,7 +215,7 @@ namespace Artemis.UI.Screens.Plugins
_notificationService.CreateNotification().WithTitle("Cleared plugin settings.").Show(); _notificationService.CreateNotification().WithTitle("Cleared plugin settings.").Show();
} }
public async Task Remove() private async Task ExecuteRemove()
{ {
bool confirmed = await _windowService.ShowConfirmContentDialog("Remove plugin", "Are you sure you want to remove this plugin?"); bool confirmed = await _windowService.ShowConfirmContentDialog("Remove plugin", "Are you sure you want to remove this plugin?");
if (!confirmed) if (!confirmed)
@ -216,7 +224,7 @@ namespace Artemis.UI.Screens.Plugins
// If the plugin or any of its features has uninstall actions, offer to run these // If the plugin or any of its features has uninstall actions, offer to run these
List<IPrerequisitesSubject> subjects = new() {Plugin.Info}; List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features); subjects.AddRange(Plugin.Features);
if (subjects.Any(s => s.Prerequisites.Any(p => p.UninstallActions.Any()))) if (subjects.Any(s => s.PlatformPrerequisites.Any(p => p.UninstallActions.Any())))
await ExecuteRemovePrerequisites(true); await ExecuteRemovePrerequisites(true);
try try
@ -232,7 +240,7 @@ namespace Artemis.UI.Screens.Plugins
_notificationService.CreateNotification().WithTitle("Removed plugin.").Show(); _notificationService.CreateNotification().WithTitle("Removed plugin.").Show();
} }
public void ShowLogsFolder() private void ExecuteShowLogsFolder()
{ {
try try
{ {
@ -244,11 +252,6 @@ namespace Artemis.UI.Screens.Plugins
} }
} }
public void OpenUri(Uri uri)
{
Utilities.OpenUrl(uri.ToString());
}
private void PluginManagementServiceOnPluginToggled(object? sender, PluginEventArgs e) private void PluginManagementServiceOnPluginToggled(object? sender, PluginEventArgs e)
{ {
this.RaisePropertyChanged(nameof(IsEnabled)); this.RaisePropertyChanged(nameof(IsEnabled));
@ -300,7 +303,7 @@ namespace Artemis.UI.Screens.Plugins
{ {
await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification() await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification()
.WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}") .WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}")
.HavingButton(b => b.WithText("View logs").WithAction(ShowLogsFolder)) .HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder))
.Show()); .Show());
} }
finally finally
@ -325,10 +328,10 @@ namespace Artemis.UI.Screens.Plugins
private void CheckPrerequisites() private void CheckPrerequisites()
{ {
CanInstallPrerequisites = Plugin.Info.Prerequisites.Any() || CanInstallPrerequisites = Plugin.Info.PlatformPrerequisites.Any() ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.Prerequisites.Any()); Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.PlatformPrerequisites.Any());
CanRemovePrerequisites = Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any()) || CanRemovePrerequisites = Plugin.Info.PlatformPrerequisites.Any(p => p.UninstallActions.Any()) ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.Prerequisites.Any(p => p.UninstallActions.Any())); Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.PlatformPrerequisites.Any(p => p.UninstallActions.Any()));
} }
} }
} }

View File

@ -30,7 +30,7 @@
<avalonia:MaterialIcon Kind="Settings" /> <avalonia:MaterialIcon Kind="Settings" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="_View Scripts" Command="{Binding ViewScripts}"> <MenuItem Header="_View Scripts" Command="{CompiledBinding ViewScripts}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="BookEdit" /> <avalonia:MaterialIcon Kind="BookEdit" />
</MenuItem.Icon> </MenuItem.Icon>

View File

@ -8,6 +8,7 @@ using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Screens.Scripting;
using Artemis.UI.Screens.Sidebar; using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
@ -54,6 +55,7 @@ public class MenuBarViewModel : ActivatableViewModelBase
AddFolder = ReactiveCommand.Create(ExecuteAddFolder); AddFolder = ReactiveCommand.Create(ExecuteAddFolder);
AddLayer = ReactiveCommand.Create(ExecuteAddLayer); AddLayer = ReactiveCommand.Create(ExecuteAddLayer);
ViewProperties = ReactiveCommand.CreateFromTask(ExecuteViewProperties, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); ViewProperties = ReactiveCommand.CreateFromTask(ExecuteViewProperties, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
ViewScripts = ReactiveCommand.CreateFromTask(ExecuteViewScripts, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
AdaptProfile = ReactiveCommand.CreateFromTask(ExecuteAdaptProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); AdaptProfile = ReactiveCommand.CreateFromTask(ExecuteAdaptProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
ToggleSuspended = ReactiveCommand.Create(ExecuteToggleSuspended, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); ToggleSuspended = ReactiveCommand.Create(ExecuteToggleSuspended, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
DeleteProfile = ReactiveCommand.CreateFromTask(ExecuteDeleteProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); DeleteProfile = ReactiveCommand.CreateFromTask(ExecuteDeleteProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null));
@ -68,6 +70,7 @@ public class MenuBarViewModel : ActivatableViewModelBase
public ReactiveCommand<Unit, Unit> AddLayer { get; } public ReactiveCommand<Unit, Unit> AddLayer { get; }
public ReactiveCommand<Unit, Unit> ToggleSuspended { get; } public ReactiveCommand<Unit, Unit> ToggleSuspended { get; }
public ReactiveCommand<Unit, Unit> ViewProperties { get; } public ReactiveCommand<Unit, Unit> ViewProperties { get; }
public ReactiveCommand<Unit, Unit> ViewScripts { get; }
public ReactiveCommand<Unit, Unit> AdaptProfile { get; } public ReactiveCommand<Unit, Unit> AdaptProfile { get; }
public ReactiveCommand<Unit, Unit> DeleteProfile { get; } public ReactiveCommand<Unit, Unit> DeleteProfile { get; }
public ReactiveCommand<Unit, Unit> ExportProfile { get; } public ReactiveCommand<Unit, Unit> ExportProfile { get; }
@ -123,6 +126,14 @@ public class MenuBarViewModel : ActivatableViewModelBase
); );
} }
private async Task ExecuteViewScripts()
{
if (ProfileConfiguration?.Profile == null)
return;
await _windowService.ShowDialogAsync<ScriptsDialogViewModel, object?>(("profile", ProfileConfiguration.Profile));
}
private async Task ExecuteAdaptProfile() private async Task ExecuteAdaptProfile()
{ {
if (ProfileConfiguration?.Profile == null) if (ProfileConfiguration?.Profile == null)

View File

@ -0,0 +1,35 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dialogs="clr-namespace:Artemis.UI.Screens.Scripting.Dialogs"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Scripting.Dialogs.ScriptConfigurationCreateView"
x:DataType="dialogs:ScriptConfigurationCreateViewModel">
<Panel>
<StackPanel IsVisible="{CompiledBinding ScriptingProviders.Count}">
<TextBlock Classes="label" Margin="0 5">Script name</TextBlock>
<TextBox Watermark="Name" Text="{CompiledBinding ScriptName}" />
<TextBlock Classes="label" Margin="0 5">Script type</TextBlock>
<ComboBox SelectedItem="{CompiledBinding SelectedScriptingProvider}" Items="{CompiledBinding ScriptingProviders}" HorizontalAlignment="Stretch">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<shared:ArtemisIcon Icon="{CompiledBinding Info.ResolvedIcon}" Width="16" Height="16" Margin="0 0 5 0" />
<TextBlock Text="{CompiledBinding LanguageName}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<TextBlock IsVisible="{CompiledBinding !ScriptingProviders.Count}">
You don't have any scripting providers installed or enabled, therefore you cannot use scripts.
</TextBlock>
</Panel>
</UserControl>

View File

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Scripting.Dialogs;
public partial class ScriptConfigurationCreateView : ReactiveUserControl<ScriptConfigurationCreateViewModel>
{
public ScriptConfigurationCreateView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using Artemis.Core.ScriptingProviders;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
namespace Artemis.UI.Screens.Scripting.Dialogs;
public class ScriptConfigurationCreateViewModel : ContentDialogViewModelBase
{
private string? _scriptName;
private ScriptingProvider _selectedScriptingProvider;
public ScriptConfigurationCreateViewModel(IScriptingService scriptingService)
{
ScriptingProviders = new List<ScriptingProvider>(scriptingService.ScriptingProviders);
Submit = ReactiveCommand.Create(ExecuteSubmit, ValidationContext.Valid);
_selectedScriptingProvider = ScriptingProviders.First();
this.ValidationRule(vm => vm.ScriptName, s => !string.IsNullOrWhiteSpace(s), "Script name cannot be empty.");
}
public ScriptConfiguration? ScriptConfiguration { get; private set; }
public List<ScriptingProvider> ScriptingProviders { get; }
public string? ScriptName
{
get => _scriptName;
set => RaiseAndSetIfChanged(ref _scriptName, value);
}
public ScriptingProvider SelectedScriptingProvider
{
get => _selectedScriptingProvider;
set => RaiseAndSetIfChanged(ref _selectedScriptingProvider, value);
}
public ReactiveCommand<Unit, Unit> Submit { get; }
private void ExecuteSubmit()
{
if (ScriptName == null)
return;
ScriptConfiguration = new ScriptConfiguration(SelectedScriptingProvider, ScriptName, ScriptType.Profile);
ContentDialog?.Hide(ContentDialogResult.Primary);
}
}

View File

@ -0,0 +1,11 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dialogs="clr-namespace:Artemis.UI.Screens.Scripting.Dialogs"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Scripting.Dialogs.ScriptConfigurationEditView"
x:DataType="dialogs:ScriptConfigurationEditViewModel">
<TextBox Watermark="Name" Name="Input" Text="{CompiledBinding ScriptName}" />
</UserControl>

View File

@ -0,0 +1,25 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using ReactiveUI;
namespace Artemis.UI.Screens.Scripting.Dialogs;
public partial class ScriptConfigurationEditView : ReactiveUserControl<ScriptConfigurationEditViewModel>
{
public ScriptConfigurationEditView()
{
InitializeComponent();
this.WhenActivated(_ =>
{
this.Get<TextBox>("Input").Focus();
this.Get<TextBox>("Input").SelectAll();
});
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,40 @@
using System.Reactive;
using Artemis.Core.ScriptingProviders;
using Artemis.UI.Shared;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
namespace Artemis.UI.Screens.Scripting.Dialogs;
public class ScriptConfigurationEditViewModel : ContentDialogViewModelBase
{
private string? _scriptName;
public ScriptConfigurationEditViewModel(ScriptConfiguration scriptConfiguration)
{
ScriptConfiguration = scriptConfiguration;
Submit = ReactiveCommand.Create(ExecuteSubmit, ValidationContext.Valid);
ScriptName = ScriptConfiguration.Name;
this.ValidationRule(vm => vm.ScriptName, s => !string.IsNullOrWhiteSpace(s), "Script name cannot be empty.");
}
public ScriptConfiguration ScriptConfiguration { get; }
public ReactiveCommand<Unit, Unit> Submit { get; }
public string? ScriptName
{
get => _scriptName;
set => RaiseAndSetIfChanged(ref _scriptName, value);
}
private void ExecuteSubmit()
{
if (ScriptName == null)
return;
ScriptConfiguration.Name = ScriptName;
ContentDialog?.Hide(ContentDialogResult.Primary);
}
}

View File

@ -0,0 +1,63 @@
using System.Reactive;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.ScriptingProviders;
using Artemis.Core.Services;
using Artemis.UI.Screens.Scripting.Dialogs;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Scripting;
public class ScriptConfigurationViewModel : ActivatableViewModelBase
{
private readonly IScriptingService _scriptingService;
private readonly IWindowService _windowService;
public ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration, IScriptingService scriptingService, IWindowService windowService)
{
_scriptingService = scriptingService;
_windowService = windowService;
ScriptConfiguration = scriptConfiguration;
Script = ScriptConfiguration.Script;
EditScriptConfiguration = ReactiveCommand.CreateFromTask<ScriptConfiguration>(ExecuteEditScriptConfiguration);
ToggleSuspended = ReactiveCommand.Create(() => ScriptConfiguration.IsSuspended = !ScriptConfiguration.IsSuspended);
}
public ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration, IScriptingService scriptingService, IWindowService windowService)
: this(scriptConfiguration, scriptingService, windowService)
{
Profile = profile;
}
public Profile? Profile { get; }
public ScriptConfiguration ScriptConfiguration { get; }
public Script? Script { get; }
public ReactiveCommand<ScriptConfiguration, Unit> EditScriptConfiguration { get; }
public ReactiveCommand<Unit, bool> ToggleSuspended { get; }
private async Task ExecuteEditScriptConfiguration(ScriptConfiguration scriptConfiguration)
{
ContentDialogResult contentDialogResult = await _windowService.CreateContentDialog()
.WithTitle("Edit script")
.WithViewModel(out ScriptConfigurationEditViewModel vm, ("scriptConfiguration", scriptConfiguration))
.WithCloseButtonText("Cancel")
.HavingPrimaryButton(b => b.WithText("Confirm").WithCommand(vm.Submit))
.HavingSecondaryButton(b => b.WithText("Delete"))
.WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
// Remove the script if the delete button was pressed
if (contentDialogResult == ContentDialogResult.Secondary)
{
if (Profile != null)
_scriptingService.RemoveScript(scriptConfiguration, Profile);
else
_scriptingService.RemoveScript(scriptConfiguration);
}
}
}

View File

@ -0,0 +1,86 @@
<controls:CoreWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:scripting="clr-namespace:Artemis.UI.Screens.Scripting"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Scripting.ScriptsDialogView"
x:DataType="scripting:ScriptsDialogViewModel"
Title="ScriptsDialogView">
<DockPanel>
<ScrollViewer DockPanel.Dock="Left" VerticalScrollBarVisibility="Auto" Width="240">
<StackPanel>
<ListBox Items="{CompiledBinding ScriptConfigurations}" SelectedItem="{CompiledBinding SelectedScript}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type scripting:ScriptConfigurationViewModel}">
<Grid ColumnDefinitions="Auto,*,Auto,Auto" RowDefinitions="*,*" Margin="4">
<shared:ArtemisIcon Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="2"
Icon="{CompiledBinding ScriptConfiguration.Script.ScriptingProvider.Info.ResolvedIcon, FallbackValue=QuestionMark}"
Width="32 "
Height="32"
Margin="0 0 10 0"
VerticalAlignment="Center" />
<TextBlock Grid.Row="0"
Grid.Column="1"
VerticalAlignment="Center"
Text="{CompiledBinding ScriptConfiguration.Name}"
IsVisible="{CompiledBinding !ScriptConfiguration.HasChanges}" />
<StackPanel Grid.Row="0"
Grid.Column="1"
VerticalAlignment="Center"
Orientation="Horizontal"
IsVisible="{CompiledBinding ScriptConfiguration.HasChanges}">
<TextBlock Text="{CompiledBinding ScriptConfiguration.Name}" FontWeight="Bold"></TextBlock>
<TextBlock Text="*"></TextBlock>
</StackPanel>
<TextBlock Grid.Row="1"
Grid.Column="1"
Text="{CompiledBinding ScriptConfiguration.Script.ScriptingProvider.LanguageName, FallbackValue='Unknown scripting provider'}"
Classes="subtitle"
FontSize="11"
VerticalAlignment="Center" />
<Button Classes="icon-button icon-button-small"
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="2"
ToolTip.Tip="Edit script"
HorizontalAlignment="Right"
Command="{CompiledBinding EditScriptConfiguration}"
CommandParameter="{CompiledBinding ScriptConfiguration}"
Margin="0 0 2 0">
<avalonia:MaterialIcon Kind="Cog" />
</Button>
<Button Classes="icon-button icon-button-small"
Command="{CompiledBinding ToggleSuspended}"
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="3"
ToolTip.Tip="Suspend/resume script">
<Panel>
<avalonia:MaterialIcon Kind="EyeOff" IsVisible="{CompiledBinding ScriptConfiguration.IsSuspended}" />
<avalonia:MaterialIcon Kind="Eye" IsVisible="{CompiledBinding !ScriptConfiguration.IsSuspended}" />
</Panel>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Add new script"
Margin="10"
HorizontalAlignment="Stretch"
Command="{CompiledBinding AddScriptConfiguration}" />
</StackPanel>
</ScrollViewer>
<Border DockPanel.Dock="Top" Classes="router-container">
<ContentControl Content="{CompiledBinding ScriptEditorViewModel}" />
</Border>
</DockPanel>
</controls:CoreWindow>

View File

@ -0,0 +1,21 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Scripting;
public partial class ScriptsDialogView : ReactiveCoreWindow<ScriptsDialogViewModel>
{
public ScriptsDialogView()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
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.ScriptingProviders;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Scripting.Dialogs;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using DynamicData;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Scripting;
public class ScriptsDialogViewModel : DialogViewModelBase<object?>
{
private readonly IScriptingService _scriptingService;
private readonly IWindowService _windowService;
private readonly Dictionary<ScriptingProvider, IScriptEditorViewModel> _providerViewModels = new();
private ObservableAsPropertyHelper<bool>? _hasScripts;
private ScriptConfigurationViewModel? _selectedScript;
private IScriptEditorViewModel? _scriptEditorViewModel;
private ReadOnlyObservableCollection<ScriptConfigurationViewModel> _scriptConfigurations;
public ScriptsDialogViewModel(IScriptingService scriptingService, IWindowService windowService, IProfileService profileService, IScriptVmFactory scriptVmFactory)
{
_scriptingService = scriptingService;
_windowService = windowService;
ScriptType = ScriptType.Global;
ScriptingProviders = new List<ScriptingProvider>(scriptingService.ScriptingProviders);
AddScriptConfiguration = ReactiveCommand.CreateFromTask(ExecuteAddScriptConfiguration, Observable.Return(ScriptingProviders.Any()));
this.WhenAnyValue(vm => vm.SelectedScript).Subscribe(s => SetupScriptEditor(s?.ScriptConfiguration));
// TODO: When not bound to a profile, base the contents of the UI on the ScriptingService
}
public ScriptsDialogViewModel(Profile profile, IScriptingService scriptingService, IWindowService windowService, IProfileService profileService, IScriptVmFactory scriptVmFactory)
: this(scriptingService, windowService, profileService, scriptVmFactory)
{
ScriptType = ScriptType.Profile;
Profile = profile;
this.WhenActivated(d =>
{
_hasScripts = Profile.ScriptConfigurations.ToObservableChangeSet()
.Count()
.Select(c => c > 0)
.ToProperty(this, vm => vm.HasScripts)
.DisposeWith(d);
Profile.ScriptConfigurations.ToObservableChangeSet()
.Transform(c => scriptVmFactory.ScriptConfigurationViewModel(Profile, c))
.Bind(out ReadOnlyObservableCollection<ScriptConfigurationViewModel> scriptConfigurationViewModels)
.Subscribe()
.DisposeWith(d);
ScriptConfigurations = scriptConfigurationViewModels;
SelectedScript = ScriptConfigurations.FirstOrDefault();
Disposable.Create(() => profileService.SaveProfile(Profile, false)).DisposeWith(d);
});
}
public ScriptType ScriptType { get; }
public List<ScriptingProvider> ScriptingProviders { get; }
public Profile? Profile { get; }
public bool HasScripts => _hasScripts?.Value ?? false;
public ReadOnlyObservableCollection<ScriptConfigurationViewModel> ScriptConfigurations
{
get => _scriptConfigurations;
set => RaiseAndSetIfChanged(ref _scriptConfigurations, value);
}
public ScriptConfigurationViewModel? SelectedScript
{
get => _selectedScript;
set => RaiseAndSetIfChanged(ref _selectedScript, value);
}
public IScriptEditorViewModel? ScriptEditorViewModel
{
get => _scriptEditorViewModel;
set => RaiseAndSetIfChanged(ref _scriptEditorViewModel, value);
}
public ReactiveCommand<Unit, Unit> AddScriptConfiguration { get; }
private void SetupScriptEditor(ScriptConfiguration? scriptConfiguration)
{
if (scriptConfiguration == null)
{
ScriptEditorViewModel = null;
return;
}
// The script is null if the provider is missing
if (scriptConfiguration.Script == null)
{
ScriptEditorViewModel = null;
return;
}
if (!_providerViewModels.TryGetValue(scriptConfiguration.Script.ScriptingProvider, out IScriptEditorViewModel? viewModel))
{
viewModel = scriptConfiguration.Script.ScriptingProvider.CreateScriptEditor(ScriptType);
_providerViewModels.Add(scriptConfiguration.Script.ScriptingProvider, viewModel);
}
ScriptEditorViewModel = viewModel;
ScriptEditorViewModel.ChangeScript(scriptConfiguration.Script);
}
private async Task ExecuteAddScriptConfiguration()
{
await _windowService.CreateContentDialog()
.WithTitle("Add script")
.WithViewModel(out ScriptConfigurationCreateViewModel vm)
.WithCloseButtonText("Cancel")
.HavingPrimaryButton(b => b.WithText("Confirm").WithCommand(vm.Submit))
.WithDefaultButton(ContentDialogButton.Primary)
.ShowAsync();
if (vm.ScriptConfiguration == null)
return;
// Add the script to the profile and instantiate it
if (Profile != null)
_scriptingService.AddScript(vm.ScriptConfiguration, Profile);
else
_scriptingService.AddScript(vm.ScriptConfiguration);
// Select the new script
SelectedScript = ScriptConfigurations.LastOrDefault();
}
}

View File

@ -15,7 +15,6 @@ using Artemis.UI.Shared;
using Avalonia; using Avalonia;
using DynamicData; using DynamicData;
using FluentAvalonia.Styling; using FluentAvalonia.Styling;
using Ninject;
using ReactiveUI; using ReactiveUI;
using Serilog.Events; using Serilog.Events;
@ -28,7 +27,7 @@ namespace Artemis.UI.Screens.Settings
private readonly IDebugService _debugService; private readonly IDebugService _debugService;
private readonly FluentAvaloniaTheme _fluentAvaloniaTheme; private readonly FluentAvaloniaTheme _fluentAvaloniaTheme;
public GeneralTabViewModel(IKernel kernel, ISettingsService settingsService, IPluginManagementService pluginManagementService, IDebugService debugService) public GeneralTabViewModel(ISettingsService settingsService, IPluginManagementService pluginManagementService, IDebugService debugService, IGraphicsContextProvider? graphicsContextProvider = null)
{ {
DisplayName = "General"; DisplayName = "General";
_settingsService = settingsService; _settingsService = settingsService;
@ -38,7 +37,6 @@ namespace Artemis.UI.Screens.Settings
List<LayerBrushProvider> layerBrushProviders = pluginManagementService.GetFeaturesOfType<LayerBrushProvider>(); List<LayerBrushProvider> layerBrushProviders = pluginManagementService.GetFeaturesOfType<LayerBrushProvider>();
LayerBrushDescriptors = new ObservableCollection<LayerBrushDescriptor>(layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors)); LayerBrushDescriptors = new ObservableCollection<LayerBrushDescriptor>(layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors));
GraphicsContexts = new ObservableCollection<string> {"Software"}; GraphicsContexts = new ObservableCollection<string> {"Software"};
IGraphicsContextProvider? graphicsContextProvider = kernel.TryGet<IGraphicsContextProvider>();
if (graphicsContextProvider != null) if (graphicsContextProvider != null)
GraphicsContexts.AddRange(graphicsContextProvider.GraphicsContextNames); GraphicsContexts.AddRange(graphicsContextProvider.GraphicsContextNames);

View File

@ -84,9 +84,9 @@
}, },
"FluentAvaloniaUI": { "FluentAvaloniaUI": {
"type": "Direct", "type": "Direct",
"requested": "[1.4.0, )", "requested": "[1.4.1, )",
"resolved": "1.4.0", "resolved": "1.4.1",
"contentHash": "K0dwenW6dbRFSnJmJAqIVWVlGIakmodgMxXWyj0gm1MoLJkuMJ5vMU/skw5X7xJJDpr88mcB4FkMPjEIp1vk9A==", "contentHash": "2m9e3YuCNa0a7EBHA9HXVq5EeA5/xtNKIJU4utMhUKHHCUgxKnBWffHUbCKzPGhhsVrVnK4Uwb/WyI8nQCHEZw==",
"dependencies": { "dependencies": {
"Avalonia": "0.10.15", "Avalonia": "0.10.15",
"Avalonia.Controls.DataGrid": "0.10.15", "Avalonia.Controls.DataGrid": "0.10.15",
@ -1726,7 +1726,7 @@
"Avalonia.ReactiveUI": "0.10.15", "Avalonia.ReactiveUI": "0.10.15",
"Avalonia.Xaml.Behaviors": "0.10.14", "Avalonia.Xaml.Behaviors": "0.10.14",
"DynamicData": "7.8.6", "DynamicData": "7.8.6",
"FluentAvaloniaUI": "1.4.0", "FluentAvaloniaUI": "1.4.1",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
"RGB.NET.Core": "1.0.0-prerelease.32", "RGB.NET.Core": "1.0.0-prerelease.32",
"ReactiveUI": "17.1.50", "ReactiveUI": "17.1.50",

View File

@ -236,8 +236,8 @@
}, },
"FluentAvaloniaUI": { "FluentAvaloniaUI": {
"type": "Transitive", "type": "Transitive",
"resolved": "1.4.0", "resolved": "1.4.1",
"contentHash": "K0dwenW6dbRFSnJmJAqIVWVlGIakmodgMxXWyj0gm1MoLJkuMJ5vMU/skw5X7xJJDpr88mcB4FkMPjEIp1vk9A==", "contentHash": "2m9e3YuCNa0a7EBHA9HXVq5EeA5/xtNKIJU4utMhUKHHCUgxKnBWffHUbCKzPGhhsVrVnK4Uwb/WyI8nQCHEZw==",
"dependencies": { "dependencies": {
"Avalonia": "0.10.15", "Avalonia": "0.10.15",
"Avalonia.Controls.DataGrid": "0.10.15", "Avalonia.Controls.DataGrid": "0.10.15",
@ -1677,7 +1677,7 @@
"Avalonia.ReactiveUI": "0.10.15", "Avalonia.ReactiveUI": "0.10.15",
"Avalonia.Xaml.Behaviors": "0.10.14", "Avalonia.Xaml.Behaviors": "0.10.14",
"DynamicData": "7.8.6", "DynamicData": "7.8.6",
"FluentAvaloniaUI": "1.4.0", "FluentAvaloniaUI": "1.4.1",
"Material.Icons.Avalonia": "1.0.2", "Material.Icons.Avalonia": "1.0.2",
"RGB.NET.Core": "1.0.0-prerelease.32", "RGB.NET.Core": "1.0.0-prerelease.32",
"ReactiveUI": "17.1.50", "ReactiveUI": "17.1.50",