mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Plugins - Ported prerequisites UI
Scripting - Ported scripting UI
This commit is contained in:
parent
0256a0d625
commit
3b4194cb9d
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core.ScriptingProviders;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
@ -15,19 +16,24 @@ namespace Artemis.Core
|
||||
private readonly object _lock = new();
|
||||
private bool _isFreshImport;
|
||||
private ProfileElement? _lastSelectedProfileElement;
|
||||
private readonly ObservableCollection<ProfileScript> _scripts;
|
||||
private readonly ObservableCollection<ScriptConfiguration> _scriptConfigurations;
|
||||
|
||||
internal Profile(ProfileConfiguration configuration, ProfileEntity profileEntity) : base(null!)
|
||||
{
|
||||
_scripts = new ObservableCollection<ProfileScript>();
|
||||
_scriptConfigurations = new ObservableCollection<ScriptConfiguration>();
|
||||
|
||||
Configuration = configuration;
|
||||
Profile = this;
|
||||
ProfileEntity = profileEntity;
|
||||
EntityId = profileEntity.Id;
|
||||
Scripts = new List<ProfileScript>();
|
||||
ScriptConfigurations = new List<ScriptConfiguration>();
|
||||
|
||||
UndoStack = new MaxStack<string>(20);
|
||||
RedoStack = new MaxStack<string>(20);
|
||||
Exceptions = new List<Exception>();
|
||||
Scripts = new ReadOnlyObservableCollection<ProfileScript>(_scripts);
|
||||
ScriptConfigurations = new ReadOnlyObservableCollection<ScriptConfiguration>(_scriptConfigurations);
|
||||
|
||||
Load();
|
||||
}
|
||||
@ -40,12 +46,12 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Gets a collection of all active scripts assigned to this profile
|
||||
/// </summary>
|
||||
public List<ProfileScript> Scripts { get; }
|
||||
public ReadOnlyObservableCollection<ProfileScript> Scripts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of all script configurations assigned to this profile
|
||||
/// </summary>
|
||||
public List<ScriptConfiguration> ScriptConfigurations { get; }
|
||||
public ReadOnlyObservableCollection<ScriptConfiguration> ScriptConfigurations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
return;
|
||||
|
||||
while (Scripts.Count > 1)
|
||||
Scripts[0].Dispose();
|
||||
while (Scripts.Count > 0)
|
||||
RemoveScript(Scripts[0]);
|
||||
|
||||
foreach (ProfileElement profileElement in Children)
|
||||
profileElement.Dispose();
|
||||
@ -208,16 +214,62 @@ namespace Artemis.Core
|
||||
else
|
||||
LastSelectedProfileElement = null;
|
||||
|
||||
foreach (ScriptConfiguration scriptConfiguration in ScriptConfigurations)
|
||||
scriptConfiguration.Script?.Dispose();
|
||||
ScriptConfigurations.Clear();
|
||||
ScriptConfigurations.AddRange(ProfileEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e)));
|
||||
while (_scriptConfigurations.Any())
|
||||
RemoveScriptConfiguration(_scriptConfigurations[0]);
|
||||
foreach (ScriptConfiguration scriptConfiguration in ProfileEntity.ScriptConfigurations.Select(e => new ScriptConfiguration(e)))
|
||||
AddScriptConfiguration(scriptConfiguration);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
if (Disposed)
|
||||
|
||||
@ -166,10 +166,13 @@ namespace Artemis.Core
|
||||
/// <inheritdoc />
|
||||
public List<PluginPrerequisite> Prerequisites { get; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PluginPrerequisite> PlatformPrerequisites => Prerequisites.Where(p => p.AppliesToPlatform());
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ArePrerequisitesMet()
|
||||
{
|
||||
return Prerequisites.All(p => p.IsMet());
|
||||
return PlatformPrerequisites.All(p => p.IsMet());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -175,11 +175,14 @@ namespace Artemis.Core
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<PluginPrerequisite> Prerequisites { get; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<PluginPrerequisite> PlatformPrerequisites => Prerequisites.Where(p => p.AppliesToPlatform());
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ArePrerequisitesMet()
|
||||
{
|
||||
return Prerequisites.All(p => p.IsMet());
|
||||
return PlatformPrerequisites.All(p => p.IsMet());
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/Artemis.Core/Plugins/PluginPlatform.cs
Normal file
20
src/Artemis.Core/Plugins/PluginPlatform.cs
Normal 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
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
@ -12,6 +11,11 @@ namespace Artemis.Core
|
||||
/// Gets a list of prerequisites for this plugin
|
||||
/// </summary>
|
||||
List<PluginPrerequisite> Prerequisites { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of prerequisites of the current platform for this plugin
|
||||
/// </summary>
|
||||
IEnumerable<PluginPrerequisite> PlatformPrerequisites { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the prerequisites of this plugin are met
|
||||
|
||||
@ -22,6 +22,11 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the platform(s) this prerequisite applies to.
|
||||
/// </summary>
|
||||
public PluginPlatform? Platform { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of actions to execute when <see cref="Install" /> is called
|
||||
/// </summary>
|
||||
@ -91,6 +96,23 @@ namespace Artemis.Core
|
||||
/// <returns><see langword="true" /> if the prerequisite is met; otherwise <see langword="false" /></returns>
|
||||
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>
|
||||
/// Called before installation starts
|
||||
/// </summary>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -36,6 +38,11 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
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 />
|
||||
public override async Task Execute(CancellationToken cancellationToken)
|
||||
{
|
||||
@ -50,10 +57,15 @@ namespace Artemis.Core
|
||||
{
|
||||
ZipArchive archive = new(fileStream);
|
||||
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();
|
||||
Progress.Report((count, archive.Entries.Count));
|
||||
Progress.Report((count, entries.Count));
|
||||
if (entry.Length > 0)
|
||||
{
|
||||
string path = Path.Combine(Target, entry.FullName);
|
||||
|
||||
@ -18,11 +18,13 @@ namespace Artemis.Core.ScriptingProviders
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ScriptConfiguration" /> class
|
||||
/// </summary>
|
||||
public ScriptConfiguration(ScriptingProvider provider, string name)
|
||||
public ScriptConfiguration(ScriptingProvider provider, string name, ScriptType scriptType)
|
||||
{
|
||||
_scriptingProviderId = provider.Id;
|
||||
_name = name;
|
||||
Entity = new ScriptConfigurationEntity();
|
||||
PendingScriptContent = provider.GetDefaultScriptContent(scriptType);
|
||||
ScriptContent = PendingScriptContent;
|
||||
}
|
||||
|
||||
internal ScriptConfiguration(ScriptConfigurationEntity entity)
|
||||
|
||||
@ -71,5 +71,11 @@ namespace Artemis.Core.ScriptingProviders
|
||||
/// </summary>
|
||||
/// <param name="scriptType">The type of script the editor will host</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -38,7 +38,7 @@ namespace Artemis.Core.ScriptingProviders
|
||||
/// <inheritdoc />
|
||||
internal override void InternalCleanup()
|
||||
{
|
||||
ScriptingService?.InternalGlobalScripts.Remove(this);
|
||||
ScriptingService?.RemoveScript(ScriptConfiguration);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -60,10 +60,7 @@ namespace Artemis.Core.ScriptingProviders
|
||||
/// <inheritdoc />
|
||||
internal override void InternalCleanup()
|
||||
{
|
||||
lock (Profile.Scripts)
|
||||
{
|
||||
Profile.Scripts.Remove(this);
|
||||
}
|
||||
Profile.RemoveScript(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -9,6 +9,7 @@ namespace Artemis.Core.ScriptingProviders
|
||||
public abstract class Script : CorePropertyChanged, IDisposable
|
||||
{
|
||||
private ScriptingProvider _scriptingProvider = null!;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// The base constructor of any script
|
||||
@ -71,6 +72,10 @@ namespace Artemis.Core.ScriptingProviders
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_disposed = true;
|
||||
ScriptConfiguration.PropertyChanged -= ScriptConfigurationOnPropertyChanged;
|
||||
ScriptConfiguration.Script = null;
|
||||
ScriptingProvider.InternalScripts.Remove(this);
|
||||
|
||||
@ -8,33 +8,40 @@ namespace Artemis.Core.Services
|
||||
/// </summary>
|
||||
public interface IScriptingService : IArtemisService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of all available scripting providers
|
||||
/// </summary>
|
||||
ReadOnlyCollection<ScriptingProvider> ScriptingProviders { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all currently active global scripts
|
||||
/// </summary>
|
||||
ReadOnlyCollection<GlobalScript> GlobalScripts { get; }
|
||||
|
||||
/// <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>
|
||||
/// <param name="scriptConfiguration">The script configuration of the script</param>
|
||||
/// <returns>
|
||||
/// If the <see cref="ScriptingProvider" /> was found an instance of the script; otherwise <see langword="null" />.
|
||||
/// </returns>
|
||||
GlobalScript? CreateScriptInstance(ScriptConfiguration scriptConfiguration);
|
||||
/// <param name="scriptConfiguration">The script configuration whose script to add.</param>
|
||||
/// <param name="profile">The profile to add the script to.</param>
|
||||
ProfileScript AddScript(ScriptConfiguration scriptConfiguration, Profile profile);
|
||||
|
||||
/// <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>
|
||||
/// <param name="profile">The profile the script configuration is configured for</param>
|
||||
/// <param name="scriptConfiguration">The script configuration of the script</param>
|
||||
/// <returns>
|
||||
/// If the <see cref="ScriptingProvider" /> was found an instance of the script; otherwise <see langword="null" />.
|
||||
/// </returns>
|
||||
ProfileScript? CreateScriptInstance(Profile profile, ScriptConfiguration scriptConfiguration);
|
||||
|
||||
/// <param name="scriptConfiguration">The script configuration whose script to remove.</param>
|
||||
/// <param name="profile">The profile to remove the script from.</param>
|
||||
void RemoveScript(ScriptConfiguration scriptConfiguration, Profile profile);
|
||||
|
||||
/// <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>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -6,29 +6,28 @@ using System.Reflection;
|
||||
using Artemis.Core.ScriptingProviders;
|
||||
using Ninject;
|
||||
using Ninject.Parameters;
|
||||
using Serilog;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
internal class ScriptingService : IScriptingService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
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;
|
||||
_profileService = profileService;
|
||||
|
||||
InternalGlobalScripts = new List<GlobalScript>();
|
||||
GlobalScripts = new ReadOnlyCollection<GlobalScript>(InternalGlobalScripts);
|
||||
|
||||
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
|
||||
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
|
||||
_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
|
||||
profileService.ProfileActivated += ProfileServiceOnProfileActivated;
|
||||
@ -39,9 +38,112 @@ namespace Artemis.Core.Services
|
||||
InitializeProfileScripts(profileConfiguration.Profile);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ReadOnlyCollection<ScriptingProvider> ScriptingProviders { get; }
|
||||
|
||||
internal List<GlobalScript> InternalGlobalScripts { 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)
|
||||
{
|
||||
// Limit to one constructor, there's no need to have more and it complicates things anyway
|
||||
@ -57,9 +159,19 @@ namespace Artemis.Core.Services
|
||||
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)
|
||||
{
|
||||
_scriptingProviders = _pluginManagementService.GetFeaturesOfType<ScriptingProvider>();
|
||||
_scriptingProviders.Clear();
|
||||
_scriptingProviders.AddRange(_pluginManagementService.GetFeaturesOfType<ScriptingProvider>());
|
||||
|
||||
foreach (ProfileConfiguration profileConfiguration in _profileService.ProfileConfigurations)
|
||||
{
|
||||
@ -74,87 +186,6 @@ namespace Artemis.Core.Services
|
||||
InitializeProfileScripts(e.ProfileConfiguration.Profile);
|
||||
}
|
||||
|
||||
private void InitializeProfileScripts(Profile profile)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -102,6 +102,16 @@ namespace Artemis.Core.Services
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of Web API controller to remove</typeparam>
|
||||
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>
|
||||
/// Adds a new EmbedIO module and restarts the web server
|
||||
|
||||
@ -7,14 +7,28 @@ namespace Artemis.Core.Services
|
||||
internal class WebModuleRegistration
|
||||
{
|
||||
public PluginFeature Feature { get; }
|
||||
public Type WebModuleType { get; }
|
||||
public Type? WebModuleType { get; }
|
||||
public Func<IWebModule>? Create { get; }
|
||||
|
||||
public WebModuleRegistration(PluginFeature feature, Type webModuleType)
|
||||
{
|
||||
Feature = feature;
|
||||
WebModuleType = webModuleType;
|
||||
Feature = feature ?? throw new ArgumentNullException(nameof(feature));
|
||||
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 :(");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -170,6 +170,20 @@ namespace Artemis.Core.Services
|
||||
|
||||
#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
|
||||
{
|
||||
if (feature == null) throw new ArgumentNullException(nameof(feature));
|
||||
|
||||
@ -34,7 +34,7 @@ namespace Artemis.UI.Linux
|
||||
private void RegisterProviders()
|
||||
{
|
||||
IInputService inputService = _kernel.Get<IInputService>();
|
||||
inputService.AddInputProvider(_kernel.Get<LinuxInputProvider>());
|
||||
// inputService.AddInputProvider(_kernel.Get<LinuxInputProvider>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -206,8 +206,8 @@
|
||||
},
|
||||
"FluentAvaloniaUI": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.4.0",
|
||||
"contentHash": "K0dwenW6dbRFSnJmJAqIVWVlGIakmodgMxXWyj0gm1MoLJkuMJ5vMU/skw5X7xJJDpr88mcB4FkMPjEIp1vk9A==",
|
||||
"resolved": "1.4.1",
|
||||
"contentHash": "2m9e3YuCNa0a7EBHA9HXVq5EeA5/xtNKIJU4utMhUKHHCUgxKnBWffHUbCKzPGhhsVrVnK4Uwb/WyI8nQCHEZw==",
|
||||
"dependencies": {
|
||||
"Avalonia": "0.10.15",
|
||||
"Avalonia.Controls.DataGrid": "0.10.15",
|
||||
@ -1718,7 +1718,7 @@
|
||||
"Avalonia.ReactiveUI": "0.10.15",
|
||||
"Avalonia.Xaml.Behaviors": "0.10.14",
|
||||
"DynamicData": "7.8.6",
|
||||
"FluentAvaloniaUI": "1.4.0",
|
||||
"FluentAvaloniaUI": "1.4.1",
|
||||
"Flurl.Http": "3.2.4",
|
||||
"Live.Avalonia": "1.3.1",
|
||||
"Material.Icons.Avalonia": "1.0.2",
|
||||
@ -1739,7 +1739,7 @@
|
||||
"Avalonia.ReactiveUI": "0.10.15",
|
||||
"Avalonia.Xaml.Behaviors": "0.10.14",
|
||||
"DynamicData": "7.8.6",
|
||||
"FluentAvaloniaUI": "1.4.0",
|
||||
"FluentAvaloniaUI": "1.4.1",
|
||||
"Material.Icons.Avalonia": "1.0.2",
|
||||
"RGB.NET.Core": "1.0.0-prerelease.32",
|
||||
"ReactiveUI": "17.1.50",
|
||||
|
||||
@ -206,8 +206,8 @@
|
||||
},
|
||||
"FluentAvaloniaUI": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.4.0",
|
||||
"contentHash": "K0dwenW6dbRFSnJmJAqIVWVlGIakmodgMxXWyj0gm1MoLJkuMJ5vMU/skw5X7xJJDpr88mcB4FkMPjEIp1vk9A==",
|
||||
"resolved": "1.4.1",
|
||||
"contentHash": "2m9e3YuCNa0a7EBHA9HXVq5EeA5/xtNKIJU4utMhUKHHCUgxKnBWffHUbCKzPGhhsVrVnK4Uwb/WyI8nQCHEZw==",
|
||||
"dependencies": {
|
||||
"Avalonia": "0.10.15",
|
||||
"Avalonia.Controls.DataGrid": "0.10.15",
|
||||
@ -1718,7 +1718,7 @@
|
||||
"Avalonia.ReactiveUI": "0.10.15",
|
||||
"Avalonia.Xaml.Behaviors": "0.10.14",
|
||||
"DynamicData": "7.8.6",
|
||||
"FluentAvaloniaUI": "1.4.0",
|
||||
"FluentAvaloniaUI": "1.4.1",
|
||||
"Flurl.Http": "3.2.4",
|
||||
"Live.Avalonia": "1.3.1",
|
||||
"Material.Icons.Avalonia": "1.0.2",
|
||||
@ -1739,7 +1739,7 @@
|
||||
"Avalonia.ReactiveUI": "0.10.15",
|
||||
"Avalonia.Xaml.Behaviors": "0.10.14",
|
||||
"DynamicData": "7.8.6",
|
||||
"FluentAvaloniaUI": "1.4.0",
|
||||
"FluentAvaloniaUI": "1.4.1",
|
||||
"Material.Icons.Avalonia": "1.0.2",
|
||||
"RGB.NET.Core": "1.0.0-prerelease.32",
|
||||
"ReactiveUI": "17.1.50",
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" />
|
||||
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.14" />
|
||||
<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="ReactiveUI" Version="17.1.50" />
|
||||
<PackageReference Include="ReactiveUI.Validation" Version="2.2.1" />
|
||||
|
||||
@ -17,7 +17,7 @@ namespace Artemis.UI.Shared.Services.Builders
|
||||
/// Creates a new instance of the <see cref="OpenFileDialogBuilder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent window that will host the dialog.</param>
|
||||
public OpenFileDialogBuilder(Window parent)
|
||||
internal OpenFileDialogBuilder(Window parent)
|
||||
{
|
||||
_parent = parent;
|
||||
_openFileDialog = new OpenFileDialog();
|
||||
|
||||
@ -17,7 +17,7 @@ namespace Artemis.UI.Shared.Services.Builders
|
||||
/// Creates a new instance of the <see cref="SaveFileDialogBuilder" /> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent window that will host the notification.</param>
|
||||
public SaveFileDialogBuilder(Window parent)
|
||||
internal SaveFileDialogBuilder(Window parent)
|
||||
{
|
||||
_parent = parent;
|
||||
_saveFileDialog = new SaveFileDialog();
|
||||
|
||||
@ -62,9 +62,9 @@
|
||||
},
|
||||
"FluentAvaloniaUI": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.4.0, )",
|
||||
"resolved": "1.4.0",
|
||||
"contentHash": "K0dwenW6dbRFSnJmJAqIVWVlGIakmodgMxXWyj0gm1MoLJkuMJ5vMU/skw5X7xJJDpr88mcB4FkMPjEIp1vk9A==",
|
||||
"requested": "[1.4.1, )",
|
||||
"resolved": "1.4.1",
|
||||
"contentHash": "2m9e3YuCNa0a7EBHA9HXVq5EeA5/xtNKIJU4utMhUKHHCUgxKnBWffHUbCKzPGhhsVrVnK4Uwb/WyI8nQCHEZw==",
|
||||
"dependencies": {
|
||||
"Avalonia": "0.10.15",
|
||||
"Avalonia.Controls.DataGrid": "0.10.15",
|
||||
|
||||
@ -232,8 +232,8 @@
|
||||
},
|
||||
"FluentAvaloniaUI": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.4.0",
|
||||
"contentHash": "K0dwenW6dbRFSnJmJAqIVWVlGIakmodgMxXWyj0gm1MoLJkuMJ5vMU/skw5X7xJJDpr88mcB4FkMPjEIp1vk9A==",
|
||||
"resolved": "1.4.1",
|
||||
"contentHash": "2m9e3YuCNa0a7EBHA9HXVq5EeA5/xtNKIJU4utMhUKHHCUgxKnBWffHUbCKzPGhhsVrVnK4Uwb/WyI8nQCHEZw==",
|
||||
"dependencies": {
|
||||
"Avalonia": "0.10.15",
|
||||
"Avalonia.Controls.DataGrid": "0.10.15",
|
||||
@ -1753,7 +1753,7 @@
|
||||
"Avalonia.ReactiveUI": "0.10.15",
|
||||
"Avalonia.Xaml.Behaviors": "0.10.14",
|
||||
"DynamicData": "7.8.6",
|
||||
"FluentAvaloniaUI": "1.4.0",
|
||||
"FluentAvaloniaUI": "1.4.1",
|
||||
"Flurl.Http": "3.2.4",
|
||||
"Live.Avalonia": "1.3.1",
|
||||
"Material.Icons.Avalonia": "1.0.2",
|
||||
@ -1774,7 +1774,7 @@
|
||||
"Avalonia.ReactiveUI": "0.10.15",
|
||||
"Avalonia.Xaml.Behaviors": "0.10.14",
|
||||
"DynamicData": "7.8.6",
|
||||
"FluentAvaloniaUI": "1.4.0",
|
||||
"FluentAvaloniaUI": "1.4.1",
|
||||
"Material.Icons.Avalonia": "1.0.2",
|
||||
"RGB.NET.Core": "1.0.0-prerelease.32",
|
||||
"ReactiveUI": "17.1.50",
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.15" />
|
||||
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.14" />
|
||||
<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="Live.Avalonia" Version="1.3.1" />
|
||||
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
|
||||
@ -71,6 +71,10 @@
|
||||
<DependentUpon>SidebarCategoryEditView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Screens\Scripting\Dialogs\ScriptConfigurationEditView.axaml.cs">
|
||||
<DependentUpon>ScriptConfigurationEditView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaXaml Update="DefaultTypes\PropertyInput\StringPropertyInputView.axaml">
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Artemis.Core.ScriptingProviders;
|
||||
using Artemis.UI.Screens.Device;
|
||||
using Artemis.UI.Screens.Plugins;
|
||||
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.Tree;
|
||||
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.Sidebar;
|
||||
using Artemis.UI.Screens.SurfaceEditor;
|
||||
@ -122,4 +125,10 @@ public interface ILayerHintVmFactory : IVmFactory
|
||||
CategoryAdaptionHintViewModel CategoryAdaptionHintViewModel(Layer layer, CategoryAdaptionHint adaptionHint);
|
||||
DeviceAdaptionHintViewModel DeviceAdaptionHintViewModel(Layer layer, DeviceAdaptionHint adaptionHint);
|
||||
KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(Layer layer, KeyboardSectionAdaptionHint adaptionHint);
|
||||
}
|
||||
|
||||
public interface IScriptVmFactory : IVmFactory
|
||||
{
|
||||
ScriptConfigurationViewModel ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration);
|
||||
ScriptConfigurationViewModel ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration);
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -9,11 +10,14 @@ using Artemis.Core;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ReactiveUI;
|
||||
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
|
||||
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public class PluginPrerequisitesInstallDialogViewModel : DialogViewModelBase<bool>
|
||||
public class PluginPrerequisitesInstallDialogViewModel : ContentDialogViewModelBase
|
||||
{
|
||||
private PluginPrerequisiteViewModel? _activePrerequisite;
|
||||
private bool _canInstall;
|
||||
@ -26,22 +30,23 @@ namespace Artemis.UI.Screens.Plugins
|
||||
public PluginPrerequisitesInstallDialogViewModel(List<IPrerequisitesSubject> subjects, IPrerequisitesVmFactory prerequisitesVmFactory)
|
||||
{
|
||||
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));
|
||||
Install = ReactiveCommand.CreateFromTask(ExecuteInstall, this.WhenAnyValue(vm => vm.CanInstall));
|
||||
|
||||
CanInstall = false;
|
||||
Task.Run(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet()));
|
||||
|
||||
Dispatcher.UIThread.Post(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet()), DispatcherPriority.Background);
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
Disposable.Create(() =>
|
||||
{
|
||||
_tokenSource?.Cancel();
|
||||
_tokenSource?.Dispose();
|
||||
_tokenSource = null;
|
||||
}).DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> Install { get; }
|
||||
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
|
||||
|
||||
public PluginPrerequisiteViewModel? ActivePrerequisite
|
||||
@ -80,13 +85,18 @@ namespace Artemis.UI.Screens.Plugins
|
||||
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;
|
||||
ShowFailed = false;
|
||||
ShowIntro = false;
|
||||
ShowProgress = true;
|
||||
|
||||
_tokenSource?.Dispose();
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
@ -110,30 +120,31 @@ namespace Artemis.UI.Screens.Plugins
|
||||
|
||||
// Wait after the task finished for the user to process what happened
|
||||
if (pluginPrerequisiteViewModel != Prerequisites.Last())
|
||||
await Task.Delay(250);
|
||||
else
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
ShowInstall = false;
|
||||
if (deferral != null)
|
||||
deferral.Complete();
|
||||
else
|
||||
ContentDialog?.Hide(ContentDialogResult.Primary);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
finally
|
||||
{
|
||||
_tokenSource.Dispose();
|
||||
_tokenSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Accept()
|
||||
public static async Task Show(IWindowService windowService, List<IPrerequisitesSubject> subjects)
|
||||
{
|
||||
Close(true);
|
||||
}
|
||||
|
||||
public static async Task<bool> Show(IWindowService windowService, List<IPrerequisitesSubject> subjects)
|
||||
{
|
||||
return await windowService.ShowDialogAsync<PluginPrerequisitesInstallDialogViewModel, bool>(("subjects", subjects));
|
||||
await windowService.CreateContentDialog()
|
||||
.WithTitle("Plugin prerequisites")
|
||||
.WithViewModel(out PluginPrerequisitesInstallDialogViewModel vm, ("subjects", subjects))
|
||||
.WithCloseButtonText("Cancel")
|
||||
.HavingPrimaryButton(b => b.WithText("Install").WithCommand(vm.Install))
|
||||
.WithDefaultButton(ContentDialogButton.Primary)
|
||||
.ShowAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -10,34 +11,38 @@ using Artemis.Core.Services;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ReactiveUI;
|
||||
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
|
||||
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public class PluginPrerequisitesUninstallDialogViewModel : DialogViewModelBase<bool>
|
||||
public class PluginPrerequisitesUninstallDialogViewModel : ContentDialogViewModelBase
|
||||
{
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly List<IPrerequisitesSubject> _subjects;
|
||||
private readonly IWindowService _windowService;
|
||||
private PluginPrerequisiteViewModel? _activePrerequisite;
|
||||
private bool _canUninstall;
|
||||
private bool _isFinished;
|
||||
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)
|
||||
{
|
||||
_subjects = subjects;
|
||||
_windowService = windowService;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
|
||||
CancelLabel = cancelLabel;
|
||||
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));
|
||||
Uninstall = ReactiveCommand.CreateFromTask(ExecuteUninstall, this.WhenAnyValue(vm => vm.CanUninstall));
|
||||
|
||||
// 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 =>
|
||||
{
|
||||
@ -45,11 +50,12 @@ namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
_tokenSource?.Cancel();
|
||||
_tokenSource?.Dispose();
|
||||
_tokenSource = null;
|
||||
}).DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
public string CancelLabel { get; }
|
||||
public ReactiveCommand<Unit, Unit> Uninstall { get; }
|
||||
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
|
||||
|
||||
public PluginPrerequisiteViewModel? ActivePrerequisite
|
||||
@ -64,20 +70,19 @@ namespace Artemis.UI.Screens.Plugins
|
||||
set => RaiseAndSetIfChanged(ref _canUninstall, value);
|
||||
}
|
||||
|
||||
public bool IsFinished
|
||||
private async Task ExecuteUninstall()
|
||||
{
|
||||
get => _isFinished;
|
||||
set => RaiseAndSetIfChanged(ref _isFinished, value);
|
||||
}
|
||||
ContentDialogClosingDeferral? deferral = null;
|
||||
if (ContentDialog != null)
|
||||
ContentDialog.Closing += (_, args) => deferral = args.GetDeferral();
|
||||
|
||||
public async Task Uninstall()
|
||||
{
|
||||
CanUninstall = false;
|
||||
|
||||
// Disable all subjects that are plugins, this will disable their features too
|
||||
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
|
||||
@ -88,9 +93,11 @@ namespace Artemis.UI.Screens.Plugins
|
||||
// Disable the parent plugin if the feature is AlwaysEnabled
|
||||
if (featureInfo.AlwaysEnabled)
|
||||
_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();
|
||||
|
||||
try
|
||||
@ -98,29 +105,34 @@ namespace Artemis.UI.Screens.Plugins
|
||||
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
|
||||
{
|
||||
pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet();
|
||||
if (!pluginPrerequisiteViewModel.IsMet) continue;
|
||||
if (!pluginPrerequisiteViewModel.IsMet)
|
||||
continue;
|
||||
|
||||
ActivePrerequisite = pluginPrerequisiteViewModel;
|
||||
await ActivePrerequisite.Uninstall(_tokenSource.Token);
|
||||
|
||||
// Wait after the task finished for the user to process what happened
|
||||
if (pluginPrerequisiteViewModel != Prerequisites.Last()) await Task.Delay(1000);
|
||||
}
|
||||
|
||||
if (Prerequisites.All(p => !p.IsMet))
|
||||
{
|
||||
IsFinished = true;
|
||||
return;
|
||||
if (pluginPrerequisiteViewModel != Prerequisites.Last())
|
||||
await Task.Delay(250);
|
||||
else
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
if (deferral != null)
|
||||
deferral.Complete();
|
||||
else
|
||||
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)
|
||||
// but at least give some feedback
|
||||
Close(false);
|
||||
await _windowService.CreateContentDialog()
|
||||
.WithTitle("Plugin prerequisites")
|
||||
.WithContent("The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.")
|
||||
.ShowAsync();
|
||||
await Show(_windowService, _subjects);
|
||||
if (Prerequisites.Any(p => p.IsMet))
|
||||
{
|
||||
await _windowService.CreateContentDialog()
|
||||
.WithTitle("Plugin prerequisites")
|
||||
.WithContent("The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.")
|
||||
.ShowAsync();
|
||||
await Show(_windowService, _subjects);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@ -129,19 +141,18 @@ namespace Artemis.UI.Screens.Plugins
|
||||
finally
|
||||
{
|
||||
CanUninstall = true;
|
||||
_tokenSource.Dispose();
|
||||
_tokenSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Accept()
|
||||
public static async Task Show(IWindowService windowService, List<IPrerequisitesSubject> subjects, string cancelLabel = "Cancel")
|
||||
{
|
||||
Close(true);
|
||||
}
|
||||
|
||||
public static async Task<object> Show(IWindowService windowService, List<IPrerequisitesSubject> subjects, string cancelLabel = "Cancel")
|
||||
{
|
||||
return await windowService.ShowDialogAsync<PluginPrerequisitesUninstallDialogViewModel, bool>(("subjects", subjects), ("cancelLabel", cancelLabel));
|
||||
await windowService.CreateContentDialog()
|
||||
.WithTitle("Plugin prerequisites")
|
||||
.WithViewModel(out PluginPrerequisitesUninstallDialogViewModel vm, ("subjects", subjects))
|
||||
.WithCloseButtonText(cancelLabel)
|
||||
.HavingPrimaryButton(b => b.WithText("Uninstall").WithCommand(vm.Uninstall))
|
||||
.WithDefaultButton(ContentDialogButton.Primary)
|
||||
.ShowAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -76,8 +76,8 @@ namespace Artemis.UI.Screens.Plugins
|
||||
}
|
||||
|
||||
public bool CanToggleEnabled => FeatureInfo.Plugin.IsEnabled && !FeatureInfo.AlwaysEnabled;
|
||||
public bool CanInstallPrerequisites => FeatureInfo.Prerequisites.Any();
|
||||
public bool CanRemovePrerequisites => FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any());
|
||||
public bool CanInstallPrerequisites => FeatureInfo.PlatformPrerequisites.Any();
|
||||
public bool CanRemovePrerequisites => FeatureInfo.PlatformPrerequisites.Any(p => p.UninstallActions.Any());
|
||||
public bool IsPopupEnabled => CanInstallPrerequisites || CanRemovePrerequisites;
|
||||
|
||||
public void ShowLogsFolder()
|
||||
@ -100,13 +100,13 @@ namespace Artemis.UI.Screens.Plugins
|
||||
|
||||
public async Task InstallPrerequisites()
|
||||
{
|
||||
if (FeatureInfo.Prerequisites.Any())
|
||||
if (FeatureInfo.PlatformPrerequisites.Any())
|
||||
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
|
||||
}
|
||||
|
||||
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});
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
27
src/Artemis.UI/Screens/Plugins/PluginPrerequisiteView.axaml
Normal file
27
src/Artemis.UI/Screens/Plugins/PluginPrerequisiteView.axaml
Normal 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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -75,7 +75,6 @@ namespace Artemis.UI.Screens.Plugins
|
||||
|
||||
public bool Busy => _busy.Value;
|
||||
public int ActiveStepNumber => _activeStepNumber.Value;
|
||||
public bool HasMultipleActions => Actions.Count > 1;
|
||||
|
||||
public async Task Install(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
@ -5,12 +5,14 @@
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
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"
|
||||
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">
|
||||
<Grid RowDefinitions="*,Auto" ColumnDefinitions="4*,5*">
|
||||
<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"
|
||||
Height="48"
|
||||
Margin="0 5 0 0"
|
||||
@ -18,52 +20,52 @@
|
||||
Grid.RowSpan="3"
|
||||
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"
|
||||
Grid.Row="1"
|
||||
Classes="subtitle"
|
||||
Text="{Binding Plugin.Info.Author}"
|
||||
IsVisible="{Binding Plugin.Info.Author, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||
Text="{CompiledBinding Plugin.Info.Author}"
|
||||
IsVisible="{CompiledBinding Plugin.Info.Author, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Grid.Row="2"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0 5"
|
||||
Text="{Binding Plugin.Info.Description}" />
|
||||
Text="{CompiledBinding Plugin.Info.Description}" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="0" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SplitButton Content="Settings" Command="{Binding OpenSettings}">
|
||||
<controls:SplitButton Content="Settings" Command="{CompiledBinding OpenSettings}">
|
||||
<controls:SplitButton.Flyout>
|
||||
<MenuFlyout Placement="Bottom">
|
||||
<MenuItem Header="Open plugin directory" Command="{Binding OpenPluginDirectory}">
|
||||
<MenuFlyout Placement="Bottom" IsOpen="{CompiledBinding IsSettingsPopupOpen, Mode=OneWayToSource}">
|
||||
<MenuItem Header="Open plugin directory" Command="{CompiledBinding OpenPluginDirectory}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="FolderOpen" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Reload plugin" Command="{Binding Reload}">
|
||||
<MenuItem Header="Reload plugin" Command="{CompiledBinding Reload}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Reload" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Install prerequisites" Command="{Binding InstallPrerequisites}">
|
||||
<MenuItem Header="Install prerequisites" Command="{CompiledBinding InstallPrerequisites}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="CheckAll" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Remove prerequisites" Command="{Binding RemovePrerequisites}">
|
||||
<MenuItem Header="Remove prerequisites" Command="{CompiledBinding RemovePrerequisites}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Delete" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Clear plugin settings" Command="{Binding RemoveSettings}">
|
||||
<MenuItem Header="Clear plugin settings" Command="{CompiledBinding RemoveSettings}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="DatabaseRemove" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Remove plugin" Command="{Binding Remove}">
|
||||
<MenuItem Header="Remove plugin" Command="{CompiledBinding Remove}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="DeleteForever" />
|
||||
</MenuItem.Icon>
|
||||
@ -74,14 +76,14 @@
|
||||
|
||||
<controls:HyperlinkButton Classes="icon-button icon-button-large"
|
||||
Margin="5 0"
|
||||
IsVisible="{Binding Plugin.Info.Website, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
NavigateUri="{Binding Plugin.Info.Website}">
|
||||
IsVisible="{CompiledBinding Plugin.Info.Website, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
NavigateUri="{CompiledBinding Plugin.Info.Website}">
|
||||
<avalonia:MaterialIcon Kind="Web" />
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Classes="icon-button icon-button-large"
|
||||
Margin="5 0"
|
||||
IsVisible="{Binding Plugin.Info.Repository, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
NavigateUri="{Binding Plugin.Info.Repository}">
|
||||
IsVisible="{CompiledBinding Plugin.Info.Repository, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
NavigateUri="{CompiledBinding Plugin.Info.Repository}">
|
||||
<avalonia:MaterialIcon Kind="Git" />
|
||||
</controls:HyperlinkButton>
|
||||
</StackPanel>
|
||||
@ -89,28 +91,28 @@
|
||||
<CheckBox Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding !Enabling}"
|
||||
IsChecked="{Binding IsEnabled}">
|
||||
IsVisible="{CompiledBinding !Enabling}"
|
||||
IsChecked="{CompiledBinding IsEnabled}">
|
||||
<StackPanel x:Name="EnableText" Orientation="Horizontal">
|
||||
<TextBlock>Plugin enabled</TextBlock>
|
||||
<avalonia:MaterialIcon Kind="ShieldHalfFull"
|
||||
Margin="5 0 0 0"
|
||||
ToolTip.Tip="Plugin requires admin rights"
|
||||
IsVisible="{Binding Plugin.Info.RequiresAdmin}" />
|
||||
IsVisible="{CompiledBinding Plugin.Info.RequiresAdmin}" />
|
||||
</StackPanel>
|
||||
</CheckBox>
|
||||
|
||||
<ProgressBar Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding Enabling}"
|
||||
IsVisible="{CompiledBinding Enabling}"
|
||||
IsIndeterminate="True" />
|
||||
</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">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<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>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
@ -49,13 +49,16 @@ namespace Artemis.UI.Screens.Plugins
|
||||
PluginFeatures = new ObservableCollection<PluginFeatureViewModel>();
|
||||
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
|
||||
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));
|
||||
RemoveSettings = ReactiveCommand.CreateFromTask(ExecuteRemoveSettings);
|
||||
Remove = ReactiveCommand.CreateFromTask(ExecuteRemove);
|
||||
InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites));
|
||||
RemovePrerequisites = ReactiveCommand.CreateFromTask<bool>(ExecuteRemovePrerequisites, this.WhenAnyValue(x => x.CanRemovePrerequisites));
|
||||
|
||||
ShowLogsFolder = ReactiveCommand.Create(ExecuteShowLogsFolder);
|
||||
OpenPluginDirectory = ReactiveCommand.Create(ExecuteOpenPluginDirectory);
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
_pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginToggled;
|
||||
@ -69,10 +72,15 @@ namespace Artemis.UI.Screens.Plugins
|
||||
});
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit,Unit> Reload { 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<bool, Unit> RemovePrerequisites { get; }
|
||||
|
||||
public ReactiveCommand<Unit,Unit> ShowLogsFolder { get; }
|
||||
public ReactiveCommand<Unit,Unit> OpenPluginDirectory { get; }
|
||||
|
||||
public ObservableCollection<PluginFeatureViewModel> PluginFeatures { get; }
|
||||
|
||||
public Plugin Plugin
|
||||
@ -137,7 +145,7 @@ namespace Artemis.UI.Screens.Plugins
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenPluginDirectory()
|
||||
private void ExecuteOpenPluginDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -149,7 +157,7 @@ namespace Artemis.UI.Screens.Plugins
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Reload()
|
||||
private async Task ExecuteReload()
|
||||
{
|
||||
bool wasEnabled = IsEnabled;
|
||||
|
||||
@ -167,28 +175,28 @@ namespace Artemis.UI.Screens.Plugins
|
||||
_notificationService.CreateNotification().WithTitle("Reloaded plugin.").Show();
|
||||
}
|
||||
|
||||
public async Task ExecuteInstallPrerequisites()
|
||||
private async Task ExecuteInstallPrerequisites()
|
||||
{
|
||||
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
|
||||
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);
|
||||
}
|
||||
|
||||
public async Task ExecuteRemovePrerequisites(bool forPluginRemoval = false)
|
||||
private async Task ExecuteRemovePrerequisites(bool forPluginRemoval = false)
|
||||
{
|
||||
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
|
||||
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");
|
||||
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?");
|
||||
if (!confirmed)
|
||||
@ -207,7 +215,7 @@ namespace Artemis.UI.Screens.Plugins
|
||||
_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?");
|
||||
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
|
||||
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
|
||||
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);
|
||||
|
||||
try
|
||||
@ -232,7 +240,7 @@ namespace Artemis.UI.Screens.Plugins
|
||||
_notificationService.CreateNotification().WithTitle("Removed plugin.").Show();
|
||||
}
|
||||
|
||||
public void ShowLogsFolder()
|
||||
private void ExecuteShowLogsFolder()
|
||||
{
|
||||
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)
|
||||
{
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
@ -300,7 +303,7 @@ namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification()
|
||||
.WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}")
|
||||
.HavingButton(b => b.WithText("View logs").WithAction(ShowLogsFolder))
|
||||
.HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder))
|
||||
.Show());
|
||||
}
|
||||
finally
|
||||
@ -325,10 +328,10 @@ namespace Artemis.UI.Screens.Plugins
|
||||
|
||||
private void CheckPrerequisites()
|
||||
{
|
||||
CanInstallPrerequisites = Plugin.Info.Prerequisites.Any() ||
|
||||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.Prerequisites.Any());
|
||||
CanRemovePrerequisites = Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any()) ||
|
||||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.Prerequisites.Any(p => p.UninstallActions.Any()));
|
||||
CanInstallPrerequisites = Plugin.Info.PlatformPrerequisites.Any() ||
|
||||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.PlatformPrerequisites.Any());
|
||||
CanRemovePrerequisites = Plugin.Info.PlatformPrerequisites.Any(p => p.UninstallActions.Any()) ||
|
||||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.PlatformPrerequisites.Any(p => p.UninstallActions.Any()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,7 @@
|
||||
<avalonia:MaterialIcon Kind="Settings" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_View Scripts" Command="{Binding ViewScripts}">
|
||||
<MenuItem Header="_View Scripts" Command="{CompiledBinding ViewScripts}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="BookEdit" />
|
||||
</MenuItem.Icon>
|
||||
|
||||
@ -8,6 +8,7 @@ using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Screens.Scripting;
|
||||
using Artemis.UI.Screens.Sidebar;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
@ -54,6 +55,7 @@ public class MenuBarViewModel : ActivatableViewModelBase
|
||||
AddFolder = ReactiveCommand.Create(ExecuteAddFolder);
|
||||
AddLayer = ReactiveCommand.Create(ExecuteAddLayer);
|
||||
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));
|
||||
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));
|
||||
@ -68,6 +70,7 @@ public class MenuBarViewModel : ActivatableViewModelBase
|
||||
public ReactiveCommand<Unit, Unit> AddLayer { get; }
|
||||
public ReactiveCommand<Unit, Unit> ToggleSuspended { get; }
|
||||
public ReactiveCommand<Unit, Unit> ViewProperties { get; }
|
||||
public ReactiveCommand<Unit, Unit> ViewScripts { get; }
|
||||
public ReactiveCommand<Unit, Unit> AdaptProfile { get; }
|
||||
public ReactiveCommand<Unit, Unit> DeleteProfile { get; }
|
||||
public ReactiveCommand<Unit, Unit> ExportProfile { get; }
|
||||
@ -122,6 +125,14 @@ public class MenuBarViewModel : ActivatableViewModelBase
|
||||
("profileConfiguration", ProfileConfiguration)
|
||||
);
|
||||
}
|
||||
|
||||
private async Task ExecuteViewScripts()
|
||||
{
|
||||
if (ProfileConfiguration?.Profile == null)
|
||||
return;
|
||||
|
||||
await _windowService.ShowDialogAsync<ScriptsDialogViewModel, object?>(("profile", ProfileConfiguration.Profile));
|
||||
}
|
||||
|
||||
private async Task ExecuteAdaptProfile()
|
||||
{
|
||||
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/Artemis.UI/Screens/Scripting/ScriptsDialogView.axaml
Normal file
86
src/Artemis.UI/Screens/Scripting/ScriptsDialogView.axaml
Normal 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>
|
||||
21
src/Artemis.UI/Screens/Scripting/ScriptsDialogView.axaml.cs
Normal file
21
src/Artemis.UI/Screens/Scripting/ScriptsDialogView.axaml.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
146
src/Artemis.UI/Screens/Scripting/ScriptsDialogViewModel.cs
Normal file
146
src/Artemis.UI/Screens/Scripting/ScriptsDialogViewModel.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@ -15,7 +15,6 @@ using Artemis.UI.Shared;
|
||||
using Avalonia;
|
||||
using DynamicData;
|
||||
using FluentAvalonia.Styling;
|
||||
using Ninject;
|
||||
using ReactiveUI;
|
||||
using Serilog.Events;
|
||||
|
||||
@ -28,7 +27,7 @@ namespace Artemis.UI.Screens.Settings
|
||||
private readonly IDebugService _debugService;
|
||||
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";
|
||||
_settingsService = settingsService;
|
||||
@ -38,7 +37,6 @@ namespace Artemis.UI.Screens.Settings
|
||||
List<LayerBrushProvider> layerBrushProviders = pluginManagementService.GetFeaturesOfType<LayerBrushProvider>();
|
||||
LayerBrushDescriptors = new ObservableCollection<LayerBrushDescriptor>(layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors));
|
||||
GraphicsContexts = new ObservableCollection<string> {"Software"};
|
||||
IGraphicsContextProvider? graphicsContextProvider = kernel.TryGet<IGraphicsContextProvider>();
|
||||
if (graphicsContextProvider != null)
|
||||
GraphicsContexts.AddRange(graphicsContextProvider.GraphicsContextNames);
|
||||
|
||||
|
||||
@ -84,9 +84,9 @@
|
||||
},
|
||||
"FluentAvaloniaUI": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.4.0, )",
|
||||
"resolved": "1.4.0",
|
||||
"contentHash": "K0dwenW6dbRFSnJmJAqIVWVlGIakmodgMxXWyj0gm1MoLJkuMJ5vMU/skw5X7xJJDpr88mcB4FkMPjEIp1vk9A==",
|
||||
"requested": "[1.4.1, )",
|
||||
"resolved": "1.4.1",
|
||||
"contentHash": "2m9e3YuCNa0a7EBHA9HXVq5EeA5/xtNKIJU4utMhUKHHCUgxKnBWffHUbCKzPGhhsVrVnK4Uwb/WyI8nQCHEZw==",
|
||||
"dependencies": {
|
||||
"Avalonia": "0.10.15",
|
||||
"Avalonia.Controls.DataGrid": "0.10.15",
|
||||
@ -1726,7 +1726,7 @@
|
||||
"Avalonia.ReactiveUI": "0.10.15",
|
||||
"Avalonia.Xaml.Behaviors": "0.10.14",
|
||||
"DynamicData": "7.8.6",
|
||||
"FluentAvaloniaUI": "1.4.0",
|
||||
"FluentAvaloniaUI": "1.4.1",
|
||||
"Material.Icons.Avalonia": "1.0.2",
|
||||
"RGB.NET.Core": "1.0.0-prerelease.32",
|
||||
"ReactiveUI": "17.1.50",
|
||||
|
||||
@ -236,8 +236,8 @@
|
||||
},
|
||||
"FluentAvaloniaUI": {
|
||||
"type": "Transitive",
|
||||
"resolved": "1.4.0",
|
||||
"contentHash": "K0dwenW6dbRFSnJmJAqIVWVlGIakmodgMxXWyj0gm1MoLJkuMJ5vMU/skw5X7xJJDpr88mcB4FkMPjEIp1vk9A==",
|
||||
"resolved": "1.4.1",
|
||||
"contentHash": "2m9e3YuCNa0a7EBHA9HXVq5EeA5/xtNKIJU4utMhUKHHCUgxKnBWffHUbCKzPGhhsVrVnK4Uwb/WyI8nQCHEZw==",
|
||||
"dependencies": {
|
||||
"Avalonia": "0.10.15",
|
||||
"Avalonia.Controls.DataGrid": "0.10.15",
|
||||
@ -1677,7 +1677,7 @@
|
||||
"Avalonia.ReactiveUI": "0.10.15",
|
||||
"Avalonia.Xaml.Behaviors": "0.10.14",
|
||||
"DynamicData": "7.8.6",
|
||||
"FluentAvaloniaUI": "1.4.0",
|
||||
"FluentAvaloniaUI": "1.4.1",
|
||||
"Material.Icons.Avalonia": "1.0.2",
|
||||
"RGB.NET.Core": "1.0.0-prerelease.32",
|
||||
"ReactiveUI": "17.1.50",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user