From 19679c0ba69853509103d6848223de03db0eb686 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 25 Apr 2021 23:18:00 +0200 Subject: [PATCH 01/19] Plugins - Added prerequisites system --- .../Artemis.Core.csproj.DotSettings | 2 + .../Extensions/StreamExtensions.cs | 133 +++++++++++++++ .../Plugins/IPluginBootstrapper.cs | 10 +- src/Artemis.Core/Plugins/Plugin.cs | 9 +- src/Artemis.Core/Plugins/PluginFeature.cs | 8 +- .../Prerequisites/PluginPrerequisite.cs | 156 ++++++++++++++++++ .../Prerequisites/PluginPrerequisiteAction.cs | 76 +++++++++ .../PrerequisiteAction/CopyFolderAction.cs | 78 +++++++++ .../PrerequisiteAction/WriteToFileAction.cs | 70 ++++++++ .../PrerequisiteActionProgress.cs | 91 ++++++++++ .../Services/PluginManagementService.cs | 3 + .../Services/ColorPickerService.cs | 2 +- .../Services/Dialog/DialogViewModelBase.cs | 4 +- .../Interfaces/IColorPickerService.cs | 2 +- .../Ninject/Factories/IVMFactory.cs | 11 +- .../Plugins/PluginPrerequisiteActionView.xaml | 26 +++ .../PluginPrerequisiteActionViewModel.cs | 71 ++++++++ .../Plugins/PluginPrerequisiteView.xaml | 37 +++++ .../Plugins/PluginPrerequisiteViewModel.cs | 142 ++++++++++++++++ .../PluginPrerequisitesDialogView.xaml | 126 ++++++++++++++ .../PluginPrerequisitesDialogViewModel.cs | 127 ++++++++++++++ .../Tabs/Plugins/PluginSettingsViewModel.cs | 39 ++++- 22 files changed, 1210 insertions(+), 13 deletions(-) create mode 100644 src/Artemis.Core/Extensions/StreamExtensions.cs create mode 100644 src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs create mode 100644 src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisiteAction.cs create mode 100644 src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/CopyFolderAction.cs create mode 100644 src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteToFileAction.cs create mode 100644 src/Artemis.Core/Plugins/Prerequisites/PrerequisiteActionProgress.cs create mode 100644 src/Artemis.UI/Screens/Plugins/PluginPrerequisiteActionView.xaml create mode 100644 src/Artemis.UI/Screens/Plugins/PluginPrerequisiteActionViewModel.cs create mode 100644 src/Artemis.UI/Screens/Plugins/PluginPrerequisiteView.xaml create mode 100644 src/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs create mode 100644 src/Artemis.UI/Screens/Plugins/PluginPrerequisitesDialogView.xaml create mode 100644 src/Artemis.UI/Screens/Plugins/PluginPrerequisitesDialogViewModel.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index f99fab4de..00c64717e 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -53,6 +53,8 @@ True True True + True + True True True True diff --git a/src/Artemis.Core/Extensions/StreamExtensions.cs b/src/Artemis.Core/Extensions/StreamExtensions.cs new file mode 100644 index 000000000..f4d5b9a5f --- /dev/null +++ b/src/Artemis.Core/Extensions/StreamExtensions.cs @@ -0,0 +1,133 @@ +// Based on: https://www.codeproject.com/Tips/5274597/An-Improved-Stream-CopyToAsync-that-Reports-Progre +// The MIT License +// +// Copyright (c) 2020 honey the codewitch +// +// Permission is hereby granted, free of charge, +// to any person obtaining a copy of this software and +// associated documentation files (the "Software"), to +// deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom +// the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Artemis.Core +{ + internal static class StreamExtensions + { + private const int DefaultBufferSize = 81920; + + /// + /// Copys a stream to another stream + /// + /// The source to copy from + /// The length of the source stream, if known - used for progress reporting + /// The destination to copy to + /// The size of the copy block buffer + /// An implementation for reporting progress + /// A cancellation token + /// A task representing the operation + public static async Task CopyToAsync( + this Stream source, + long sourceLength, + Stream destination, + int bufferSize, + IProgress<(long, long)> progress, + CancellationToken cancellationToken) + { + if (0 == bufferSize) + bufferSize = DefaultBufferSize; + byte[]? buffer = new byte[bufferSize]; + if (0 > sourceLength && source.CanSeek) + sourceLength = source.Length - source.Position; + long totalBytesCopied = 0L; + if (null != progress) + progress.Report((totalBytesCopied, sourceLength)); + int bytesRead = -1; + while (0 != bytesRead && !cancellationToken.IsCancellationRequested) + { + bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken); + if (0 == bytesRead || cancellationToken.IsCancellationRequested) + break; + await destination.WriteAsync(buffer, 0, buffer.Length, cancellationToken); + totalBytesCopied += bytesRead; + progress?.Report((totalBytesCopied, sourceLength)); + } + + if (0 < totalBytesCopied) + progress?.Report((totalBytesCopied, sourceLength)); + cancellationToken.ThrowIfCancellationRequested(); + } + + /// + /// Copys a stream to another stream + /// + /// The source to copy from + /// The length of the source stream, if known - used for progress reporting + /// The destination to copy to + /// An implementation for reporting progress + /// A cancellation token + /// A task representing the operation + public static Task CopyToAsync(this Stream source, long sourceLength, Stream destination, IProgress<(long, long)> progress, CancellationToken cancellationToken) + { + return CopyToAsync(source, sourceLength, destination, 0, progress, cancellationToken); + } + + /// + /// Copys a stream to another stream + /// + /// The source to copy from + /// The destination to copy to + /// An implementation for reporting progress + /// A cancellation token + /// A task representing the operation + public static Task CopyToAsync(this Stream source, Stream destination, IProgress<(long, long)> progress, CancellationToken cancellationToken) + { + return CopyToAsync(source, 0L, destination, 0, progress, cancellationToken); + } + + /// + /// Copys a stream to another stream + /// + /// The source to copy from + /// The length of the source stream, if known - used for progress reporting + /// The destination to copy to + /// An implementation for reporting progress + /// A task representing the operation + public static Task CopyToAsync(this Stream source, long sourceLength, Stream destination, IProgress<(long, long)> progress) + { + return CopyToAsync(source, sourceLength, destination, 0, progress, default); + } + + /// + /// Copys a stream to another stream + /// + /// The source to copy from + /// The destination to copy to + /// An implementation for reporting progress + /// A task representing the operation + public static Task CopyToAsync(this Stream source, Stream destination, IProgress<(long, long)> progress) + { + return CopyToAsync(source, 0L, destination, 0, progress, default); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/IPluginBootstrapper.cs b/src/Artemis.Core/Plugins/IPluginBootstrapper.cs index af4b607d7..bc2c20cb0 100644 --- a/src/Artemis.Core/Plugins/IPluginBootstrapper.cs +++ b/src/Artemis.Core/Plugins/IPluginBootstrapper.cs @@ -5,16 +5,22 @@ /// public interface IPluginBootstrapper { + /// + /// Called when the plugin is loaded + /// + /// + void OnPluginLoaded(Plugin plugin); + /// /// Called when the plugin is activated /// /// The plugin instance of your plugin - void Enable(Plugin plugin); + void OnPluginEnabled(Plugin plugin); /// /// Called when the plugin is deactivated or when Artemis shuts down /// /// The plugin instance of your plugin - void Disable(Plugin plugin); + void OnPluginDisabled(Plugin plugin); } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index da4e3f7aa..03403cf51 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -78,6 +78,11 @@ namespace Artemis.Core /// public IKernel? Kernel { get; internal set; } + /// + /// Gets a list of prerequisites for this plugin feature + /// + public List Prerequisites { get; } = new(); + /// /// The PluginLoader backing this plugin /// @@ -235,12 +240,12 @@ namespace Artemis.Core if (enable) { - Bootstrapper?.Enable(this); + Bootstrapper?.OnPluginEnabled(this); OnEnabled(); } else { - Bootstrapper?.Disable(this); + Bootstrapper?.OnPluginDisabled(this); OnDisabled(); } } diff --git a/src/Artemis.Core/Plugins/PluginFeature.cs b/src/Artemis.Core/Plugins/PluginFeature.cs index 8b9943a0a..dbe5a87d0 100644 --- a/src/Artemis.Core/Plugins/PluginFeature.cs +++ b/src/Artemis.Core/Plugins/PluginFeature.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Threading.Tasks; @@ -15,7 +16,7 @@ namespace Artemis.Core private readonly Stopwatch _updateStopwatch = new(); private bool _isEnabled; private Exception? _loadException; - + /// /// Gets the plugin feature info related to this feature /// @@ -59,6 +60,11 @@ namespace Artemis.Core /// public TimeSpan RenderTime { get; private set; } + /// + /// Gets a list of prerequisites for this plugin feature + /// + public List Prerequisites { get; } = new(); + internal PluginFeatureEntity Entity { get; set; } = null!; // Will be set right after construction /// diff --git a/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs b/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs new file mode 100644 index 000000000..ea272786b --- /dev/null +++ b/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs @@ -0,0 +1,156 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Artemis.Core +{ + /// + /// Represents a prerequisite for a or + /// + public abstract class PluginPrerequisite : CorePropertyChanged + { + private PluginPrerequisiteAction? _currentAction; + + /// + /// Creates a new instance of the class + /// + /// The plugin this is a prerequisite for + protected PluginPrerequisite(Plugin plugin) + { + Plugin = plugin; + } + + /// + /// Creates a new instance of the class + /// + /// The plugin feature this is a prerequisite for + protected PluginPrerequisite(PluginFeature pluginFeature) + { + PluginFeature = pluginFeature; + } + + /// + /// Gets the name of the prerequisite + /// + public abstract string Name { get; } + + /// + /// Gets the description of the prerequisite + /// + public abstract string Description { get; } + + /// + /// Gets a boolean indicating whether installing or uninstalling this prerequisite requires admin privileges + /// + public abstract bool RequiresElevation { get; } + + /// + /// Gets a list of actions to execute when is called + /// + public abstract List InstallActions { get; } + + /// + /// Gets a list of actions to execute when is called + /// + public abstract List UninstallActions { get; } + + /// + /// Gets or sets the action currently being executed + /// + public PluginPrerequisiteAction? CurrentAction + { + get => _currentAction; + private set => SetAndNotify(ref _currentAction, value); + } + + /// + /// Gets or sets the plugin this prerequisite is for + /// Note: Only one plugin or a plugin feature can be set at once + /// + public Plugin? Plugin { get; } + + /// + /// Gets or sets the feature this prerequisite is for + /// Note: Only one plugin or a plugin feature can be set at once + /// + public PluginFeature? PluginFeature { get; } + + /// + /// Execute all install actions + /// + public async Task Install(CancellationToken cancellationToken) + { + try + { + OnInstallStarting(); + foreach (PluginPrerequisiteAction installAction in InstallActions) + { + cancellationToken.ThrowIfCancellationRequested(); + CurrentAction = installAction; + await installAction.Execute(cancellationToken); + } + } + finally + { + CurrentAction = null; + OnInstallFinished(); + } + } + + /// + /// Execute all uninstall actions + /// + public async Task Uninstall(CancellationToken cancellationToken) + { + try + { + OnUninstallStarting(); + foreach (PluginPrerequisiteAction uninstallAction in UninstallActions) + { + cancellationToken.ThrowIfCancellationRequested(); + CurrentAction = uninstallAction; + await uninstallAction.Execute(cancellationToken); + } + } + finally + { + CurrentAction = null; + OnUninstallFinished(); + } + } + + /// + /// Called to determine whether the prerequisite is met + /// + /// if the prerequisite is met; otherwise + public abstract Task IsMet(); + + /// + /// Called before installation starts + /// + protected virtual void OnInstallStarting() + { + } + + /// + /// Called after installation finishes + /// + protected virtual void OnInstallFinished() + { + } + + /// + /// Called before uninstall starts + /// + protected virtual void OnUninstallStarting() + { + } + + /// + /// Called after uninstall finished + /// + protected virtual void OnUninstallFinished() + { + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisiteAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisiteAction.cs new file mode 100644 index 000000000..344d4412d --- /dev/null +++ b/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisiteAction.cs @@ -0,0 +1,76 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Artemis.Core +{ + /// + /// Represents an action that must be taken to install or uninstall a plugin prerequisite + /// + public abstract class PluginPrerequisiteAction : CorePropertyChanged + { + private bool _progressIndeterminate; + private string? _status; + private bool _subProgressIndeterminate; + + /// + /// The base constructor for all plugin prerequisite actions + /// + /// The name of the action + protected PluginPrerequisiteAction(string name) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + } + + #region Implementation of IPluginPrerequisiteAction + + /// + /// Gets the name of the action + /// + public string Name { get; } + + /// + /// Gets or sets the status of the action + /// + public string? Status + { + get => _status; + set => SetAndNotify(ref _status, value); + } + + /// + /// Gets or sets a boolean indicating whether the progress is indeterminate or not + /// + public bool ProgressIndeterminate + { + get => _progressIndeterminate; + set => SetAndNotify(ref _progressIndeterminate, value); + } + + /// + /// Gets or sets a boolean indicating whether the progress is indeterminate or not + /// + public bool SubProgressIndeterminate + { + get => _subProgressIndeterminate; + set => SetAndNotify(ref _subProgressIndeterminate, value); + } + + /// + /// Gets or sets the progress of the action (0 to 100) + /// + public PrerequisiteActionProgress Progress { get; } = new(); + + /// + /// Gets or sets the sub progress of the action + /// + public PrerequisiteActionProgress SubProgress { get; } = new(); + + /// + /// Called when the action must execute + /// + public abstract Task Execute(CancellationToken cancellationToken); + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/CopyFolderAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/CopyFolderAction.cs new file mode 100644 index 000000000..7a18f5f08 --- /dev/null +++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/CopyFolderAction.cs @@ -0,0 +1,78 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Humanizer; + +namespace Artemis.Core +{ + /// + /// Represents a plugin prerequisite action that copies a folder + /// + public class CopyFolderAction : PluginPrerequisiteAction + { + /// + /// Creates a new instance of a copy folder action + /// + /// The name of the action + /// The source folder to copy + /// The target folder to copy to (will be created if needed) + public CopyFolderAction(string name, string source, string target) : base(name) + { + Source = source; + Target = target; + } + + /// + /// Gets the source directory + /// + public string Source { get; } + + /// + /// Gets or sets the target directory + /// + public string Target { get; } + + /// + public override async Task Execute(CancellationToken cancellationToken) + { + DirectoryInfo source = new(Source); + DirectoryInfo target = new(Target); + + if (!source.Exists) + throw new ArtemisCoreException($"The source directory at '{source}' was not found."); + + int filesCopied = 0; + FileInfo[] files = source.GetFiles("*", SearchOption.AllDirectories); + + foreach (FileInfo fileInfo in files) + { + string outputPath = fileInfo.FullName.Replace(source.FullName, target.FullName); + string outputDir = Path.GetDirectoryName(outputPath)!; + Utilities.CreateAccessibleDirectory(outputDir); + + void SubProgressOnProgressReported(object? sender, EventArgs e) + { + if (SubProgress.ProgressPerSecond != 0) + Status = $"Copying {fileInfo.Name} - {SubProgress.ProgressPerSecond.Bytes().Humanize("#.##")}/sec"; + else + Status = $"Copying {fileInfo.Name}"; + } + + Progress.Report((filesCopied, files.Length)); + SubProgress.ProgressReported += SubProgressOnProgressReported; + + await using FileStream sourceStream = fileInfo.OpenRead(); + await using FileStream destinationStream = File.Create(outputPath); + + await sourceStream.CopyToAsync(fileInfo.Length, destinationStream, SubProgress, cancellationToken); + + filesCopied++; + SubProgress.ProgressReported -= SubProgressOnProgressReported; + } + + Progress.Report((filesCopied, files.Length)); + Status = $"Finished copying {filesCopied} file(s)"; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteToFileAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteToFileAction.cs new file mode 100644 index 000000000..0dedc3f28 --- /dev/null +++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteToFileAction.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Artemis.Core +{ + /// + /// Represents a plugin prerequisite action that copies a folder + /// + public class WriteToFileAction : PluginPrerequisiteAction + { + /// + /// Creates a new instance of a copy folder action + /// + /// The name of the action + /// The target file to write to (will be created if needed) + /// The contents to write + public WriteToFileAction(string name, string target, string content) : base(name) + { + Target = target ?? throw new ArgumentNullException(nameof(target)); + Content = content ?? throw new ArgumentNullException(nameof(content)); + } + + /// + /// Creates a new instance of a copy folder action + /// + /// The name of the action + /// The target file to write to (will be created if needed) + /// The contents to write + public WriteToFileAction(string name, string target, byte[] content) : base(name) + { + Target = target; + ByteContent = content ?? throw new ArgumentNullException(nameof(content)); + } + + /// + /// Gets or sets the target file + /// + public string Target { get; } + + /// + /// Gets the contents that will be written + /// + public string? Content { get; } + + /// + /// Gets the bytes that will be written + /// + public byte[]? ByteContent { get; } + + /// + public override async Task Execute(CancellationToken cancellationToken) + { + string outputDir = Path.GetDirectoryName(Target)!; + Utilities.CreateAccessibleDirectory(outputDir); + + ProgressIndeterminate = true; + Status = $"Writing to {Path.GetFileName(Target)}..."; + + if (Content != null) + await File.WriteAllTextAsync(Target, Content, cancellationToken); + else if (ByteContent != null) + await File.WriteAllBytesAsync(Target, ByteContent, cancellationToken); + + ProgressIndeterminate = false; + Status = $"Finished writing to {Path.GetFileName(Target)}"; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteActionProgress.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteActionProgress.cs new file mode 100644 index 000000000..96b48bde3 --- /dev/null +++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteActionProgress.cs @@ -0,0 +1,91 @@ +using System; + +namespace Artemis.Core +{ + /// + /// Represents progress on a plugin prerequisite action + /// + public class PrerequisiteActionProgress : CorePropertyChanged, IProgress<(long, long)> + { + private long _current; + private DateTime _lastReport; + private double _percentage; + private double _progressPerSecond; + private long _total; + private long _lastReportValue; + + /// + /// The current amount + /// + public long Current + { + get => _current; + set => SetAndNotify(ref _current, value); + } + + /// + /// The total amount + /// + public long Total + { + get => _total; + set => SetAndNotify(ref _total, value); + } + + /// + /// The percentage + /// + public double Percentage + { + get => _percentage; + set => SetAndNotify(ref _percentage, value); + } + + /// + /// Gets or sets the progress per second + /// + public double ProgressPerSecond + { + get => _progressPerSecond; + set => SetAndNotify(ref _progressPerSecond, value); + } + + #region Implementation of IProgress + + /// + public void Report((long, long) value) + { + (long newCurrent, long newTotal) = value; + + TimeSpan timePassed = DateTime.Now - _lastReport; + if (timePassed >= TimeSpan.FromSeconds(1)) + { + ProgressPerSecond = Math.Max(0, Math.Round(1.0 / timePassed.TotalSeconds * (newCurrent - _lastReportValue), 2)); + _lastReportValue = newCurrent; + _lastReport = DateTime.Now; + } + + Current = newCurrent; + Total = newTotal; + Percentage = Math.Round((double) Current / Total * 100.0, 2); + + OnProgressReported(); + } + + #endregion + + #region Events + + /// + /// Occurs when progress has been reported + /// + public event EventHandler? ProgressReported; + + protected virtual void OnProgressReported() + { + ProgressReported?.Invoke(this, EventArgs.Empty); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 4f833282d..9fcd8829a 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -340,7 +340,10 @@ namespace Artemis.Core.Services if (bootstrappers.Count > 1) _logger.Warning($"{plugin} has more than one bootstrapper, only initializing {bootstrappers.First().FullName}"); if (bootstrappers.Any()) + { plugin.Bootstrapper = (IPluginBootstrapper?) Activator.CreateInstance(bootstrappers.First()); + plugin.Bootstrapper?.OnPluginLoaded(plugin); + } lock (_plugins) { diff --git a/src/Artemis.UI.Shared/Services/ColorPickerService.cs b/src/Artemis.UI.Shared/Services/ColorPickerService.cs index a3602c1b7..bacb62802 100644 --- a/src/Artemis.UI.Shared/Services/ColorPickerService.cs +++ b/src/Artemis.UI.Shared/Services/ColorPickerService.cs @@ -53,7 +53,7 @@ namespace Artemis.UI.Shared.Services public LinkedList RecentColors => RecentColorsSetting.Value; - public Task ShowGradientPicker(ColorGradient colorGradient, string dialogHost) + public Task ShowGradientPicker(ColorGradient colorGradient, string dialogHost) { if (!string.IsNullOrWhiteSpace(dialogHost)) return _dialogService.ShowDialogAt(dialogHost, new Dictionary {{"colorGradient", colorGradient}}); diff --git a/src/Artemis.UI.Shared/Services/Dialog/DialogViewModelBase.cs b/src/Artemis.UI.Shared/Services/Dialog/DialogViewModelBase.cs index 3cdddba3b..81c7fbbf0 100644 --- a/src/Artemis.UI.Shared/Services/Dialog/DialogViewModelBase.cs +++ b/src/Artemis.UI.Shared/Services/Dialog/DialogViewModelBase.cs @@ -6,7 +6,7 @@ namespace Artemis.UI.Shared.Services /// /// Represents the base class for a dialog view model /// - public abstract class DialogViewModelBase : ValidatingModelBase + public abstract class DialogViewModelBase : Screen { private DialogViewModelHost? _dialogViewModelHost; private DialogSession? _session; @@ -47,6 +47,7 @@ namespace Artemis.UI.Shared.Services /// public virtual void OnDialogClosed(object sender, DialogClosingEventArgs e) { + ScreenExtensions.TryClose(this); } /// @@ -61,6 +62,7 @@ namespace Artemis.UI.Shared.Services internal void OnDialogOpened(object sender, DialogOpenedEventArgs e) { Session = e.Session; + ScreenExtensions.TryActivate(this); } } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IColorPickerService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IColorPickerService.cs index a5bbc26f8..6fcee45e3 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IColorPickerService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IColorPickerService.cs @@ -7,7 +7,7 @@ namespace Artemis.UI.Shared.Services { internal interface IColorPickerService : IArtemisSharedUIService { - Task ShowGradientPicker(ColorGradient colorGradient, string dialogHost); + Task ShowGradientPicker(ColorGradient colorGradient, string dialogHost); PluginSetting PreviewSetting { get; } LinkedList RecentColors { get; } diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index e24134dce..d5d54ef50 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -2,6 +2,7 @@ using Artemis.Core.Modules; using Artemis.UI.Screens.Modules; using Artemis.UI.Screens.Modules.Tabs; +using Artemis.UI.Screens.Plugins; using Artemis.UI.Screens.ProfileEditor; using Artemis.UI.Screens.ProfileEditor.Conditions; using Artemis.UI.Screens.ProfileEditor.LayerProperties; @@ -95,7 +96,13 @@ namespace Artemis.UI.Ninject.Factories TimelineSegmentViewModel TimelineSegmentViewModel(SegmentViewModelType segment, IObservableCollection layerPropertyGroups); } - public interface IDataBindingsVmFactory + public interface IPrerequisitesVmFactory : IVmFactory + { + PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite); + } + + // TODO: Move these two + public interface IDataBindingsVmFactory { IDataBindingViewModel DataBindingViewModel(IDataBindingRegistration registration); DirectDataBindingModeViewModel DirectDataBindingModeViewModel(DirectDataBinding directDataBinding); @@ -104,7 +111,7 @@ namespace Artemis.UI.Ninject.Factories DataBindingConditionViewModel DataBindingConditionViewModel(DataBindingCondition dataBindingCondition); } - public interface IPropertyVmFactory + public interface IPropertyVmFactory { ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel); ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel); diff --git a/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteActionView.xaml b/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteActionView.xaml new file mode 100644 index 000000000..31ec31f24 --- /dev/null +++ b/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteActionView.xaml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteActionViewModel.cs b/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteActionViewModel.cs new file mode 100644 index 000000000..279414a6a --- /dev/null +++ b/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteActionViewModel.cs @@ -0,0 +1,71 @@ +using System; +using System.ComponentModel; +using Artemis.Core; +using Stylet; + +namespace Artemis.UI.Screens.Plugins +{ + public class PluginPrerequisiteActionViewModel : Screen + { + private bool _showProgressBar; + private bool _showSubProgressBar; + + public PluginPrerequisiteActionViewModel(PluginPrerequisiteAction action) + { + Action = action; + } + + public PluginPrerequisiteAction Action { get; } + + public bool ShowProgressBar + { + get => _showProgressBar; + set => SetAndNotify(ref _showProgressBar, value); + } + + public bool ShowSubProgressBar + { + get => _showSubProgressBar; + set => SetAndNotify(ref _showSubProgressBar, value); + } + + private void ActionOnPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Action.ProgressIndeterminate) || e.PropertyName == nameof(Action.SubProgressIndeterminate)) + UpdateProgress(); + } + + private void ProgressReported(object? sender, EventArgs e) + { + UpdateProgress(); + } + + private void UpdateProgress() + { + ShowSubProgressBar = Action.SubProgress.Percentage != 0 || Action.SubProgressIndeterminate; + ShowProgressBar = ShowSubProgressBar || Action.Progress.Percentage != 0 || Action.ProgressIndeterminate; + } + + #region Overrides of Screen + + /// + protected override void OnInitialActivate() + { + Action.Progress.ProgressReported += ProgressReported; + Action.SubProgress.ProgressReported += ProgressReported; + Action.PropertyChanged += ActionOnPropertyChanged; + base.OnInitialActivate(); + } + + /// + protected override void OnClose() + { + Action.Progress.ProgressReported -= ProgressReported; + Action.SubProgress.ProgressReported -= ProgressReported; + Action.PropertyChanged -= ActionOnPropertyChanged; + base.OnClose(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteView.xaml b/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteView.xaml new file mode 100644 index 000000000..3049b6c16 --- /dev/null +++ b/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteView.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs b/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs new file mode 100644 index 000000000..b70b97ba4 --- /dev/null +++ b/src/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs @@ -0,0 +1,142 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.UI.Shared.Services; +using Stylet; + +namespace Artemis.UI.Screens.Plugins +{ + public class PluginPrerequisiteViewModel : Conductor.Collection.OneActive + { + private readonly ICoreService _coreService; + private readonly IDialogService _dialogService; + private bool _installing; + private bool _uninstalling; + private bool _isMet; + + public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, ICoreService coreService, IDialogService dialogService) + { + _coreService = coreService; + _dialogService = dialogService; + + PluginPrerequisite = pluginPrerequisite; + } + + public PluginPrerequisite PluginPrerequisite { get; } + + public bool Installing + { + get => _installing; + set + { + SetAndNotify(ref _installing, value); + NotifyOfPropertyChange(nameof(Busy)); + } + } + + public bool Uninstalling + { + get => _uninstalling; + set + { + SetAndNotify(ref _uninstalling, value); + NotifyOfPropertyChange(nameof(Busy)); + } + } + + public bool IsMet + { + get => _isMet; + set => SetAndNotify(ref _isMet, value); + } + + public bool Busy => Installing || Uninstalling; + public int ActiveStemNumber => Items.IndexOf(ActiveItem) + 1; + public bool HasMultipleActions => Items.Count > 1; + + public async Task Install(CancellationToken cancellationToken) + { + if (Busy) + return; + + if (PluginPrerequisite.RequiresElevation && !_coreService.IsElevated) + { + await _dialogService.ShowConfirmDialog("Install plugin prerequisite", "This plugin prerequisite admin rights to install (restart & elevate NYI)"); + return; + } + + Installing = true; + try + { + await PluginPrerequisite.Install(cancellationToken); + } + finally + { + Installing = false; + IsMet = await PluginPrerequisite.IsMet(); + } + } + + public async Task Uninstall(CancellationToken cancellationToken) + { + if (Busy) + return; + + if (PluginPrerequisite.RequiresElevation && !_coreService.IsElevated) + { + await _dialogService.ShowConfirmDialog("Install plugin prerequisite", "This plugin prerequisite admin rights to install (restart & elevate NYI)"); + return; + } + + Uninstalling = true; + try + { + await PluginPrerequisite.Uninstall(cancellationToken); + } + finally + { + Uninstalling = false; + IsMet = await PluginPrerequisite.IsMet(); + } + } + + private void PluginPrerequisiteOnPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(PluginPrerequisite.CurrentAction)) + ActivateCurrentAction(); + } + + private void ActivateCurrentAction() + { + ActiveItem = Items.FirstOrDefault(i => i.Action == PluginPrerequisite.CurrentAction); + NotifyOfPropertyChange(nameof(ActiveStemNumber)); + } + + #region Overrides of Screen + + /// + protected override void OnClose() + { + PluginPrerequisite.PropertyChanged += PluginPrerequisiteOnPropertyChanged; + base.OnClose(); + } + + /// + protected override void OnInitialActivate() + { + PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged; + Task.Run(async () => IsMet = await PluginPrerequisite.IsMet()); + + Items.AddRange(PluginPrerequisite.InstallActions.Select(a => new PluginPrerequisiteActionViewModel(a))); + Items.AddRange(PluginPrerequisite.UninstallActions.Select(a => new PluginPrerequisiteActionViewModel(a))); + + base.OnInitialActivate(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/PluginPrerequisitesDialogView.xaml b/src/Artemis.UI/Screens/Plugins/PluginPrerequisitesDialogView.xaml new file mode 100644 index 000000000..124406e0b --- /dev/null +++ b/src/Artemis.UI/Screens/Plugins/PluginPrerequisitesDialogView.xaml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + Plugin prerequisites + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + In order for this plugin to function certain prerequisites must be met. + On the left side you can see all prerequisites and whether they are currently met or not. + Clicking install prerequisites will automatically set everything up for you. + + + + + + + + + Selected stop: @@ -122,8 +157,12 @@ Margin="8,0,0,0" IsEnabled="{Binding HasSelectedColorStopViewModel}" Command="{s:Action RemoveColorStop}" - CommandParameter="{Binding SelectedColorStopViewModel}"> - DELETE + CommandParameter="{Binding SelectedColorStopViewModel}" + ToolTip="Delete Selected Stop"> + + + Delete + diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs index 497d81963..fb170cc33 100644 --- a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs +++ b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs @@ -27,6 +27,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor PropertyChanged += UpdateColorStopViewModels; ColorGradient.CollectionChanged += ColorGradientOnCollectionChanged; + ColorStopViewModels.CollectionChanged += ColorStopViewModelsOnCollectionChanged; } #region Overrides of DialogViewModelBase @@ -53,6 +54,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor } public bool HasSelectedColorStopViewModel => SelectedColorStopViewModel != null; + public bool HasMoreThanOneStop => ColorStopViewModels.Count > 1; public ColorGradient ColorGradient { get; } @@ -79,6 +81,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor ColorStopViewModels.Insert(index, viewModel); SelectColorStop(viewModel); + NotifyOfPropertyChange(nameof(HasMoreThanOneStop)); } public void RemoveColorStop(ColorStopViewModel colorStopViewModel) @@ -90,6 +93,38 @@ namespace Artemis.UI.Shared.Screens.GradientEditor ColorGradient.Remove(colorStopViewModel.ColorStop); SelectColorStop(null); + NotifyOfPropertyChange(nameof(HasMoreThanOneStop)); + } + + public void SpreadColorStops() + { + var stops = ColorStopViewModels.OrderBy(x => x.OffsetFloat); + int index = 0; + foreach (ColorStopViewModel stop in stops) + { + stop.OffsetFloat = index / ((float)stops.Count() - 1); + index++; + } + } + + public void RotateColorStops() + { + var stops = ColorStopViewModels.OrderByDescending(x => x.OffsetFloat); + float lastStopPosition = stops.Last().OffsetFloat; + foreach (ColorStopViewModel stop in stops) + { + float tempStop = stop.OffsetFloat; + stop.OffsetFloat = lastStopPosition; + lastStopPosition = tempStop; + } + } + + public void FlipColorStops() + { + foreach (ColorStopViewModel stop in ColorStopViewModels) + { + stop.OffsetFloat = 1 - stop.OffsetFloat; + } } public Point GetPositionInPreview(object sender, MouseEventArgs e) @@ -127,10 +162,15 @@ namespace Artemis.UI.Shared.Screens.GradientEditor foreach (ColorGradientStop colorStop in ColorGradient) ColorStopViewModels.Add(new ColorStopViewModel(this, colorStop)); } - + private void ColorGradientOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { NotifyOfPropertyChange(nameof(ColorGradient)); } + + private void ColorStopViewModelsOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + NotifyOfPropertyChange(nameof(HasMoreThanOneStop)); + } } } \ No newline at end of file From 0cfddcbbaf0c0fd14f1ba1c738a8166aa7b8bd67 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 28 Apr 2021 00:32:42 +0200 Subject: [PATCH 03/19] Plugin prerequisites - Added UI for installing/removing prerequisites Plugin settings - Added button to reload plugin from disk Confirm dialogs - Made cancel text optional so you can hide the button --- .../ArtemisPluginPrerequisiteException.cs | 38 +++++ src/Artemis.Core/Plugins/Plugin.cs | 8 + .../Prerequisites/PluginPrerequisite.cs | 2 +- .../Prerequisites/PluginPrerequisiteAction.cs | 20 +++ .../PrerequisiteAction/CopyFolderAction.cs | 3 + .../PrerequisiteAction/DeleteFileAction.cs | 44 +++++ .../PrerequisiteAction/DeleteFolderAction.cs | 49 ++++++ .../WriteBytesToFileAction.cs | 62 +++++++ ...leAction.cs => WriteStringToFileAction.cs} | 42 ++--- .../Services/PluginManagementService.cs | 14 +- .../Screens/Dialogs/ConfirmDialogView.xaml | 13 +- .../Services/Dialog/DialogService.cs | 10 +- src/Artemis.UI/Artemis.UI.csproj | 3 + .../Ninject/Factories/IVMFactory.cs | 2 +- .../Plugins/PluginPrerequisiteActionView.xaml | 4 +- .../PluginPrerequisiteActionViewModel.cs | 54 +----- .../Plugins/PluginPrerequisiteViewModel.cs | 18 +- ...PluginPrerequisitesInstallDialogView.xaml} | 4 +- ...ginPrerequisitesInstallDialogViewModel.cs} | 37 ++++- ...luginPrerequisitesUninstallDialogView.xaml | 125 ++++++++++++++ ...inPrerequisitesUninstallDialogViewModel.cs | 154 ++++++++++++++++++ .../Tabs/Devices/DeviceSettingsView.xaml | 2 +- .../Tabs/Plugins/PluginSettingsTabView.xaml | 1 - .../Tabs/Plugins/PluginSettingsView.xaml | 86 +++++++--- .../Tabs/Plugins/PluginSettingsViewModel.cs | 104 +++++++++--- 25 files changed, 741 insertions(+), 158 deletions(-) create mode 100644 src/Artemis.Core/Exceptions/ArtemisPluginPrerequisiteException.cs create mode 100644 src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DeleteFileAction.cs create mode 100644 src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DeleteFolderAction.cs create mode 100644 src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteBytesToFileAction.cs rename src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/{WriteToFileAction.cs => WriteStringToFileAction.cs} (55%) rename src/Artemis.UI/Screens/Plugins/{PluginPrerequisitesDialogView.xaml => PluginPrerequisitesInstallDialogView.xaml} (99%) rename src/Artemis.UI/Screens/Plugins/{PluginPrerequisitesDialogViewModel.cs => PluginPrerequisitesInstallDialogViewModel.cs} (66%) create mode 100644 src/Artemis.UI/Screens/Plugins/PluginPrerequisitesUninstallDialogView.xaml create mode 100644 src/Artemis.UI/Screens/Plugins/PluginPrerequisitesUninstallDialogViewModel.cs diff --git a/src/Artemis.Core/Exceptions/ArtemisPluginPrerequisiteException.cs b/src/Artemis.Core/Exceptions/ArtemisPluginPrerequisiteException.cs new file mode 100644 index 000000000..fd2dbe169 --- /dev/null +++ b/src/Artemis.Core/Exceptions/ArtemisPluginPrerequisiteException.cs @@ -0,0 +1,38 @@ +using System; + +namespace Artemis.Core +{ + /// + /// An exception thrown when a plugin prerequisite-related error occurs + /// + public class ArtemisPluginPrerequisiteException : Exception + { + internal ArtemisPluginPrerequisiteException(Plugin plugin, PluginPrerequisite? pluginPrerequisite) + { + Plugin = plugin; + PluginPrerequisite = pluginPrerequisite; + } + + internal ArtemisPluginPrerequisiteException(Plugin plugin, PluginPrerequisite? pluginPrerequisite, string message) : base(message) + { + Plugin = plugin; + PluginPrerequisite = pluginPrerequisite; + } + + internal ArtemisPluginPrerequisiteException(Plugin plugin, PluginPrerequisite? pluginPrerequisite, string message, Exception inner) : base(message, inner) + { + Plugin = plugin; + PluginPrerequisite = pluginPrerequisite; + } + + /// + /// Gets the plugin the error is related to + /// + public Plugin Plugin { get; } + + /// + /// Gets the plugin prerequisite the error is related to + /// + public PluginPrerequisite? PluginPrerequisite { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index 03403cf51..a8dbaf7ad 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -125,6 +125,14 @@ namespace Artemis.Core return Info.ToString(); } + /// + /// Determines whether the prerequisites of this plugin are met + /// + public bool ArePrerequisitesMet() + { + return Prerequisites.All(p => p.IsMet()); + } + /// /// Occurs when the plugin is enabled /// diff --git a/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs b/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs index ea272786b..5267b4d33 100644 --- a/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs +++ b/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs @@ -123,7 +123,7 @@ namespace Artemis.Core /// Called to determine whether the prerequisite is met /// /// if the prerequisite is met; otherwise - public abstract Task IsMet(); + public abstract bool IsMet(); /// /// Called before installation starts diff --git a/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisiteAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisiteAction.cs index 344d4412d..90d9a787e 100644 --- a/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisiteAction.cs +++ b/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisiteAction.cs @@ -10,6 +10,8 @@ namespace Artemis.Core public abstract class PluginPrerequisiteAction : CorePropertyChanged { private bool _progressIndeterminate; + private bool _showProgressBar; + private bool _showSubProgressBar; private string? _status; private bool _subProgressIndeterminate; @@ -56,6 +58,24 @@ namespace Artemis.Core set => SetAndNotify(ref _subProgressIndeterminate, value); } + /// + /// Gets or sets a boolean indicating whether the progress bar should be shown + /// + public bool ShowProgressBar + { + get => _showProgressBar; + set => SetAndNotify(ref _showProgressBar, value); + } + + /// + /// Gets or sets a boolean indicating whether the sub progress bar should be shown + /// + public bool ShowSubProgressBar + { + get => _showSubProgressBar; + set => SetAndNotify(ref _showSubProgressBar, value); + } + /// /// Gets or sets the progress of the action (0 to 100) /// diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/CopyFolderAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/CopyFolderAction.cs index 7a18f5f08..6602567e0 100644 --- a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/CopyFolderAction.cs +++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/CopyFolderAction.cs @@ -21,6 +21,9 @@ namespace Artemis.Core { Source = source; Target = target; + + ShowProgressBar = true; + ShowSubProgressBar = true; } /// diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DeleteFileAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DeleteFileAction.cs new file mode 100644 index 000000000..e6324bf8f --- /dev/null +++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DeleteFileAction.cs @@ -0,0 +1,44 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Artemis.Core +{ + /// + /// Represents a plugin prerequisite action that deletes a file + /// + public class DeleteFileAction : PluginPrerequisiteAction + { + /// + /// Creates a new instance of a copy folder action + /// + /// The name of the action + /// The target folder to delete recursively + public DeleteFileAction(string name, string target) : base(name) + { + Target = target; + ProgressIndeterminate = true; + } + + /// + /// Gets or sets the target directory + /// + public string Target { get; } + + /// + public override async Task Execute(CancellationToken cancellationToken) + { + ShowProgressBar = true; + Status = $"Removing {Target}"; + + await Task.Run(() => + { + if (File.Exists(Target)) + File.Delete(Target); + }, cancellationToken); + + ShowProgressBar = false; + Status = $"Removed {Target}"; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DeleteFolderAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DeleteFolderAction.cs new file mode 100644 index 000000000..62b4dfc41 --- /dev/null +++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DeleteFolderAction.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Artemis.Core +{ + /// + /// Represents a plugin prerequisite action that recursively deletes a folder + /// + public class DeleteFolderAction : PluginPrerequisiteAction + { + /// + /// Creates a new instance of a copy folder action + /// + /// The name of the action + /// The target folder to delete recursively + public DeleteFolderAction(string name, string target) : base(name) + { + if (Enum.GetValues().Select(Environment.GetFolderPath).Contains(target)) + throw new ArtemisCoreException($"Cannot delete special folder {target}, silly goose."); + + Target = target; + ProgressIndeterminate = true; + } + + /// + /// Gets or sets the target directory + /// + public string Target { get; } + + /// + public override async Task Execute(CancellationToken cancellationToken) + { + ShowProgressBar = true; + Status = $"Removing {Target}"; + + await Task.Run(() => + { + if (Directory.Exists(Target)) + Directory.Delete(Target, true); + }, cancellationToken); + + ShowProgressBar = false; + Status = $"Removed {Target}"; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteBytesToFileAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteBytesToFileAction.cs new file mode 100644 index 000000000..92c7d0294 --- /dev/null +++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteBytesToFileAction.cs @@ -0,0 +1,62 @@ +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Artemis.Core +{ + /// + /// Represents a plugin prerequisite action that copies a folder + /// + public class WriteBytesToFileAction : PluginPrerequisiteAction + { + /// + /// Creates a new instance of a copy folder action + /// + /// The name of the action + /// The target file to write to (will be created if needed) + /// The contents to write + public WriteBytesToFileAction(string name, string target, byte[] content) : base(name) + { + Target = target; + ByteContent = content ?? throw new ArgumentNullException(nameof(content)); + } + + /// + /// Gets or sets the target file + /// + public string Target { get; } + + /// + /// Gets or sets a boolean indicating whether or not to append to the file if it exists already, if set to + /// the file will be deleted and recreated + /// + public bool Append { get; set; } = false; + + /// + /// Gets the bytes that will be written + /// + public byte[] ByteContent { get; } + + /// + public override async Task Execute(CancellationToken cancellationToken) + { + string outputDir = Path.GetDirectoryName(Target)!; + Utilities.CreateAccessibleDirectory(outputDir); + + ShowProgressBar = true; + Status = $"Writing to {Path.GetFileName(Target)}..."; + + if (!Append && File.Exists(Target)) + File.Delete(Target); + + await using Stream fileStream = File.OpenWrite(Target); + await using MemoryStream sourceStream = new(ByteContent); + await sourceStream.CopyToAsync(sourceStream.Length, fileStream, Progress, cancellationToken); + + ShowProgressBar = false; + Status = $"Finished writing to {Path.GetFileName(Target)}"; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteToFileAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteStringToFileAction.cs similarity index 55% rename from src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteToFileAction.cs rename to src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteStringToFileAction.cs index 0dedc3f28..e4e6a9875 100644 --- a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteToFileAction.cs +++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteStringToFileAction.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -8,7 +9,7 @@ namespace Artemis.Core /// /// Represents a plugin prerequisite action that copies a folder /// - public class WriteToFileAction : PluginPrerequisiteAction + public class WriteStringToFileAction : PluginPrerequisiteAction { /// /// Creates a new instance of a copy folder action @@ -16,22 +17,12 @@ namespace Artemis.Core /// The name of the action /// The target file to write to (will be created if needed) /// The contents to write - public WriteToFileAction(string name, string target, string content) : base(name) - { - Target = target ?? throw new ArgumentNullException(nameof(target)); - Content = content ?? throw new ArgumentNullException(nameof(content)); - } - - /// - /// Creates a new instance of a copy folder action - /// - /// The name of the action - /// The target file to write to (will be created if needed) - /// The contents to write - public WriteToFileAction(string name, string target, byte[] content) : base(name) + public WriteStringToFileAction(string name, string target, string content) : base(name) { Target = target; - ByteContent = content ?? throw new ArgumentNullException(nameof(content)); + Content = content ?? throw new ArgumentNullException(nameof(content)); + + ProgressIndeterminate = true; } /// @@ -40,30 +31,31 @@ namespace Artemis.Core public string Target { get; } /// - /// Gets the contents that will be written + /// Gets or sets a boolean indicating whether or not to append to the file if it exists already, if set to + /// the file will be deleted and recreated /// - public string? Content { get; } + public bool Append { get; set; } = false; /// - /// Gets the bytes that will be written + /// Gets the string that will be written /// - public byte[]? ByteContent { get; } - + public string Content { get; } + /// public override async Task Execute(CancellationToken cancellationToken) { string outputDir = Path.GetDirectoryName(Target)!; Utilities.CreateAccessibleDirectory(outputDir); - ProgressIndeterminate = true; + ShowProgressBar = true; Status = $"Writing to {Path.GetFileName(Target)}..."; - if (Content != null) + if (Append) + await File.AppendAllTextAsync(Target, Content, cancellationToken); + else await File.WriteAllTextAsync(Target, Content, cancellationToken); - else if (ByteContent != null) - await File.WriteAllBytesAsync(Target, ByteContent, cancellationToken); - ProgressIndeterminate = false; + ShowProgressBar = false; Status = $"Finished writing to {Path.GetFileName(Target)}"; } } diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 9fcd8829a..a46c1e3fb 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -241,7 +241,16 @@ namespace Artemis.Core.Services } foreach (Plugin plugin in _plugins.Where(p => p.Entity.IsEnabled)) - EnablePlugin(plugin, false, ignorePluginLock); + { + try + { + EnablePlugin(plugin, false, ignorePluginLock); + } + catch (ArtemisPluginPrerequisiteException) + { + _logger.Warning("Skipped enabling plugin {plugin} because not all prerequisites are met", plugin); + } + } _logger.Debug("Enabled {count} plugin(s)", _plugins.Count(p => p.IsEnabled)); // ReSharper restore InconsistentlySynchronizedField @@ -372,6 +381,9 @@ namespace Artemis.Core.Services return; } + if (!plugin.ArePrerequisitesMet()) + throw new ArtemisPluginPrerequisiteException(plugin, null, "Cannot enable a plugin whose prerequisites aren't all met"); + // Create the Ninject child kernel and load the module plugin.Kernel = new ChildKernel(_kernel, new PluginModule(plugin)); OnPluginEnabling(new PluginEventArgs(plugin)); diff --git a/src/Artemis.UI.Shared/Screens/Dialogs/ConfirmDialogView.xaml b/src/Artemis.UI.Shared/Screens/Dialogs/ConfirmDialogView.xaml index 9fced07e6..a65e58422 100644 --- a/src/Artemis.UI.Shared/Screens/Dialogs/ConfirmDialogView.xaml +++ b/src/Artemis.UI.Shared/Screens/Dialogs/ConfirmDialogView.xaml @@ -1,14 +1,18 @@ - + d:DataContext="{d:DesignInstance {x:Type dialogs:ConfirmDialogViewModel}}"> + + + + Content="{Binding CancelText}" + Visibility="{Binding CancelText, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}" /> - - + + + + + + + + + + + + + diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs index f025488c1..e3aa2f065 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs @@ -10,23 +10,24 @@ using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.Plugins; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; -using MaterialDesignThemes.Wpf; using Ninject; using Stylet; -using Constants = Artemis.Core.Constants; namespace Artemis.UI.Screens.Settings.Tabs.Plugins { public class PluginSettingsViewModel : Conductor.Collection.AllActive { + private readonly ICoreService _coreService; private readonly IDialogService _dialogService; + private readonly IMessageService _messageService; private readonly IPluginManagementService _pluginManagementService; private readonly ISettingsVmFactory _settingsVmFactory; - private readonly ICoreService _coreService; - private readonly IMessageService _messageService; private readonly IWindowManager _windowManager; private bool _enabling; private Plugin _plugin; + private bool _isSettingsPopupOpen; + private bool _canInstallPrerequisites; + private bool _canRemovePrerequisites; public PluginSettingsViewModel(Plugin plugin, ISettingsVmFactory settingsVmFactory, @@ -70,6 +71,28 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins set => Task.Run(() => UpdateEnabled(value)); } + public bool IsSettingsPopupOpen + { + get => _isSettingsPopupOpen; + set + { + if (!SetAndNotify(ref _isSettingsPopupOpen, value)) return; + CheckPrerequisites(); + } + } + + public bool CanInstallPrerequisites + { + get => _canInstallPrerequisites; + set => SetAndNotify(ref _canInstallPrerequisites, value); + } + + public bool CanRemovePrerequisites + { + get => _canRemovePrerequisites; + set => SetAndNotify(ref _canRemovePrerequisites, value); + } + public void OpenSettings() { PluginConfigurationDialog configurationViewModel = (PluginConfigurationDialog) Plugin.ConfigurationDialog; @@ -88,6 +111,49 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins } } + public void OpenPluginDirectory() + { + try + { + Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Plugin.Directory.FullName); + } + catch (Exception e) + { + _dialogService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e); + } + } + + public async Task Reload() + { + bool wasEnabled = IsEnabled; + + _pluginManagementService.UnloadPlugin(Plugin); + Items.Clear(); + + Plugin = _pluginManagementService.LoadPlugin(Plugin.Directory); + foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features) + Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false)); + + if (wasEnabled) + await UpdateEnabled(true); + } + + public async Task InstallPrerequisites() + { + if (Plugin.Prerequisites.Any()) + await ShowPrerequisitesDialog(false); + } + + public async Task RemovePrerequisites() + { + if (Plugin.Prerequisites.Any(p => p.UninstallActions.Any())) + { + await ShowPrerequisitesDialog(true); + NotifyOfPropertyChange(nameof(IsEnabled)); + NotifyOfPropertyChange(nameof(CanOpenSettings)); + } + } + public async Task RemoveSettings() { bool confirmed = await _dialogService.ShowConfirmDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?"); @@ -109,7 +175,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins public async Task Remove() { - bool confirmed = await _dialogService.ShowConfirmDialog("Delete plugin", "Are you sure you want to delete this plugin?"); + bool confirmed = await _dialogService.ShowConfirmDialog("Remove plugin", "Are you sure you want to remove this plugin?"); if (!confirmed) return; @@ -170,10 +236,10 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins } // Check if all prerequisites are met async - if (!await ArePrerequisitesMetAsync()) + if (!Plugin.ArePrerequisitesMet()) { - await _dialogService.ShowDialog(new Dictionary {{"pluginOrFeature", Plugin}}); - if (!await ArePrerequisitesMetAsync()) + await ShowPrerequisitesDialog(false); + if (!Plugin.ArePrerequisitesMet()) { CancelEnable(); return; @@ -194,9 +260,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins } } else - { _pluginManagementService.DisablePlugin(Plugin, true); - } NotifyOfPropertyChange(nameof(IsEnabled)); NotifyOfPropertyChange(nameof(CanOpenSettings)); @@ -209,19 +273,17 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins NotifyOfPropertyChange(nameof(CanOpenSettings)); } - private async Task ArePrerequisitesMetAsync() + private void CheckPrerequisites() { - bool needsPrerequisites = false; - foreach (PluginPrerequisite pluginPrerequisite in Plugin.Prerequisites) - { - if (await pluginPrerequisite.IsMet()) - continue; + CanInstallPrerequisites = Plugin.Prerequisites.Any(); + CanRemovePrerequisites = Plugin.Prerequisites.Any(p => p.UninstallActions.Any()); + } - needsPrerequisites = true; - break; - } - - return !needsPrerequisites; + private async Task ShowPrerequisitesDialog(bool uninstall) + { + if (uninstall) + return await _dialogService.ShowDialog(new Dictionary { { "pluginOrFeature", Plugin } }); + return await _dialogService.ShowDialog(new Dictionary { { "pluginOrFeature", Plugin } }); } } } \ No newline at end of file From a874a494ff33143efe1fdb0e232901d1c682c346 Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Wed, 28 Apr 2021 09:49:35 -0400 Subject: [PATCH 04/19] Add more gradient tools --- .../GradientEditor/GradientEditorView.xaml | 51 ++++++++++++++----- .../GradientEditor/GradientEditorViewModel.cs | 38 ++++++++++++++ 2 files changed, 76 insertions(+), 13 deletions(-) diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorView.xaml b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorView.xaml index 2d8a50a73..32236519e 100644 --- a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorView.xaml +++ b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorView.xaml @@ -12,8 +12,8 @@ Background="{DynamicResource MaterialDesignPaper}" FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto" Width="400" - Height="425" - d:DesignHeight="425" + Height="450" + d:DesignHeight="450" d:DesignWidth="400" d:DataContext="{d:DesignInstance local:GradientEditorViewModel}"> @@ -90,41 +90,66 @@ - + + + + + + Selected stop: @@ -135,7 +160,7 @@ - + - x.OffsetFloat).Last(); + + if (stop == SelectedColorStopViewModel) SelectColorStop(null); + + ColorStopViewModels.Remove(stop); + ColorGradient.Remove(stop.ColorStop); + + // Distribute the stops if there is still more than one + if (ColorGradient.Count > 1) + SpreadColorStops(); + } + else + { + // Add a stop to the end that is the same color as the first stop + ColorGradientStop stop = new(ColorGradient.First().Color, 100); + ColorGradient.Add(stop); + + ColorStopViewModel viewModel = new(this, stop); + ColorStopViewModels.Add(viewModel); + + NotifyOfPropertyChange(nameof(HasMoreThanOneStop)); + + // Distribute the stops + SpreadColorStops(); + } + } + public void ClearGradient() + { + ColorGradient.Clear(); + ColorStopViewModels.Clear(); + } + public Point GetPositionInPreview(object sender, MouseEventArgs e) { Canvas? parent = VisualTreeUtilities.FindParent((DependencyObject) sender, null); From e82c031f3b5ed7c465384deaf873163b45e6e01e Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Wed, 28 Apr 2021 09:51:00 -0400 Subject: [PATCH 05/19] Color Picker Textbox QoL Change Make the text always uppercase and make the font monospace to make sure it's visible in low width environments (ie. the gradient picker) --- src/Artemis.UI.Shared/Controls/ColorPicker.xaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Artemis.UI.Shared/Controls/ColorPicker.xaml b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml index 740f659f0..c28b32576 100644 --- a/src/Artemis.UI.Shared/Controls/ColorPicker.xaml +++ b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml @@ -73,7 +73,9 @@ MinWidth="95" MaxLength="9" Margin="0" - HorizontalAlignment="Stretch"> + HorizontalAlignment="Stretch" + FontFamily="Consolas" + CharacterCasing="Upper"> + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Resources/Images/Logo/bow.ico b/src/Artemis.UI/Resources/Images/Logo/bow.ico new file mode 100644 index 0000000000000000000000000000000000000000..d3b788ab753150d3f14f6c6221a06c9103323032 GIT binary patch literal 35937 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 - - - -Created by potrace 1.15, written by Peter Selinger 2001-2017 - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Resources/Images/Logo/logo-512.ico b/src/Artemis.UI/Resources/Images/Logo/logo-512.ico deleted file mode 100644 index 015d1ffa8531e7e0a01ff368edb15cec40428223..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112887 zcmeEP2V70q3=Oss27S_;l>I7Y5@t0RNKW^DzMo*5!o;BLKJ1^W-sMFzZM(n7N2B{}x5Xdg(bG-h3-Wa+dFZ7LZa38 zqXrgaqssd4Khl(Y6fMBkT>K2b&PlDvb!YJydohQvVE3J|EX)>MncbmV_oIy(AEV&Y zj6*k0-M+xHuLRc>4G*o}NdIMUO!oniGFRELEbr8$)ZILM?>x+SC_FK=woM=L?!&*A z>vsdjqAaKh#>)7(dZjot=xN8jV4 zo>VJ+3P2ClmcX6$N_yr6XqY8oQ8x#bX=UE?8gBP?scF@7-VhrhRhd$aGZYbSeVZ%m;L`7Ih!TS`;x2r9_D({8d^8|K1Fhlz(CW*h9yLxH`M zMP{dWpS7_s*6@xG(;FU_3LmDEz-rh7J}ut}!#3CnyoW6RfRtg$0Gb|iSrj2Q=FJWT zSXpXdGG9i<9%GrqrDz{rjH0mHw_|6K?o1UYet7kr0u=!^)rYIJGAi#BD52e3#K8`F znCNLWUKyBMdE-gYjVY+Mz9wK=#C#lf)*mO#$d`vf)SOUA4+m3r2PF(WZ3iE=5f8&t zLUalO%bQPN*uU&_#-kQAffwdCh0h)wyL37CxrJBtpl4QtkoE;u4U$cGzD#9Tw_>YP zWAYdu7i=r~K*i-Co%6JT!=s1o0ZY?;rCa$OqV6FfnRFRE3?FQ25-%o3VPpspX>Q*( z=JkH>Ie~K(FE!^oHQzf>>0_ZAv+!=_$>Pjx*H$XhE6l=|VZXc~sZ*f&F^?w>CG6;A zbWC5dVg!{N*AZSGhPMRh6z%7f@pV$3l@Sr8%Bh(fT{kn5xlMt|!{?En7Cgx!rClxj z8e2d{lfo?+J;KTG$l+V#HRJT1lqF#5%7nu21|B#W%?lP6W1$r9ADOjRk%r&j|Z-m8~$OGf-8u}G|?%p}%ZtZxkfmP+wJLQWM zJ9x2;(3=C;g4mTc@9tw%NzNAIOwwn+HcJgY0S&B#Eop?4vPW=*Ye==6ceB#IB7W%) z5|ww~%lq;B`x8-7nBhKwO}999`D@2?y83WzXJ=Dsp<(2!#n9>>KIw`xA`^v6NWi<{ z7M&5kD=l%%9`3wXv07Cm+XM10H z#a&zQ*!A)F-zi^b*hq-pf~9bYst->;Ls`Oiq+`P-44;sp$s@1}ySXy%3ds%UM~NNk z42rmRytcg1G?}X|P*kU%g>-K2UUy8%{irg9nDToOH#OKo*<5Q}h5L`Iu${EyL8qai zNu`PFWX?ECq+(|dN`4yu*29GXn4&ONv~l=hVJv33h}R{@vIx)#V7ixxq~8a(nK-L) zXg6-z`fhqKOQEk1gMyMat6j^YwjiO)|9#37mxs0m5z+p$0%qMw(e;6eO%*xZq+~UE zaoYlh&+>6nZS;Kl^7ajt8mEy4ruo~&N;-j}nYX1xt6B9Qi*0T(ys)dic{}~y11#|q zEKDpf5-hUDCfn{*1WRQTGs!U?U_4M(N%Vw(5_Vru&_`jE#voL8UuKGd-Lq)r_EUF# z`R!OD@-YO)dpFKY+}Ydbd){Jvw>%X+m;2B;=Tn7s3R>D}RIkk66nGI)!o&rHvrc$D z5q5FNo*cN-Tb^=rz@m7p?=;rE{+>6LiW>!~POvC32w7L=nRn8PZhyathL74KkN#t7 zUS(pE*3M1kWmHnTjg5I@E|6MnHZxN9!F|?PG$<6@y%-Z?z!R8@X~%oE!3LHnt@a}D zU1=+ya!#S_X{9}e3VvN=z6a$8w>Q3~-rb7UO?hJH-CT8>4ReZkSo(Nab~Lt*t{hs4 z@m?k`W2XhGYecv%iwIOY2h7B@;h3GV;bS=k8*e*;PFw9|$>hYrVf4tw>W00!n6Ss& zu$X;iQ7o3@HP_tTn5>WFi`tQs*EVf3Hptk=!vMbG6%CS&@db(5uw&|vN*bE7e4F)l z3) z?}e#Y(;*F^-*0H}b?Ko_5(wdM&iw zx|L&gLFSYWX+(IqlY_(gld4D6R8_CNcH#GTBrr8;E_UL-c?uo4Dz$A=VbeGzX6pWp zyc7w|YBnMfabmU8+cxX&;3_+AY>?pA$;DKtFNepn(<3kq{P z>qdxkgiDg^!bA-?f}ZRUY~#OM{A6DIaSI{33{7sXrAtekt$^<-vFi+3A)PUeJ?}~t z<;qRR6yqQ6f!XI~e2i3Aa_2B*HZ^TvyVDy=na?+p{=WQu)cDApP@5V>9rzT6_RE}I zxLWvRcu^yBvjY#=-VgS-8yTjS@s1y@>{(YhOKG(y4$!V z#zkJ+ySJCrtMdTm(fd z@F=ia9F~+cyJ?f@h{+4})7iUIgR?d^##9X1?2MaN6(x(j0oDo)y7fk4SDoPii4zt< z_#mg7ls>6d^Ma-n!P}}{gFSm3$);~cq;;@RqTd949OJFpg)u|w%8NV02a|?Lxs5X4 zr0nwy3%T&<#J%pi!O5b4xUMewL2Pl$7y%plsL`#Jd1vgXgJpAsu|C5_>;p<0U`Jxzh^jLeGJ*Bm*$Z>)X9W0xWT-1xsi#lp&m(SgQBe7Q=wCLEBr(m0T_h2ek zGHt$gIis2SjVc?46}e}md}46d=*Qf%+p@U(eV!K8E0$35QfxIlb8-ufG%d_+0hhW{ z9lx)VF|Syr-I~d1y5i+~XOCP7N4iT*rNNTh@Y}_LJuow}UJKaKgbzGAdNd;Exdk<7 z!CYgt>|}2V9CZ?SZobo5ktV%Wy{zkDiENB)Sp7_k>LFjgQj!O0AWQPs00>fl5Fp}7Jo|gK?9IbCNw~m&U_Khh5<)asEUS_yjo+G&}$4JL$JO7T? z$H#|}nb{%n3GbQqsbAaC?fFAYa`y~q4cR0zN1~(CM%&KOsuB@hw{ggHPE&EEq4;vqPg1pIeqKumD5`oRh4pU^k%+k~Z z9t{Z=EFlB69D^#B7DfaxlO3#z2SYtfnZ~uy36C1EPW9Jj)&|oR7I)x_1=8{zJy89i zMO@f5Q9@d%CmR3Sa8tb^&aFbU6Vk_r33VMNX~zc*BI_w%N0IR{K-e}5!Q%CTumh`z zMaV|*pOCaqU5%x=8C^R)AB@k;?Ra9R)BM9l^yUdWDcVDn#`VwlF;p70G=u~c4f~FD zJ(Vu6uED6U*$Ue1jkf#h@nNZoWUpuarxLhD_AsC14$2C-y(>IY*V$z{q@ACc+l9PH zI_Ggg;G5Az%ar{moc1{ec@9>wPmj!-o)g3ohD9^8jugs1Xil{Yi(`k_Xz-nd=KdQR+` zL$^C6LhK!FQYZU_YQ=PjfujbGoA`qS+-eJ~*kj7u@RDbxC~@wzJIcnG)GA!qvwdZ>m^<3da|=k#>b)KtO8nJgo0s5MGnr%$~j z9e{>4)~I}?WrM*hQ)rHK>vmhln$6#k_LT zBJ%@&-V3JIf$9^z?m{glv=*is$njf_zk+Z z%cdKZZGKWpn>=eK3NZ^Ik}=+?NQaXT~TLMDd@&0 zM>F2zx(9Iyc{BNfO>>W{qM^RtwL@yLVV)1KHBk>dXb6>M4_bIt5;wm%dSaJM7|$tmFGv

8{hpF)zr^8O})wiZiz!YC36MNV2otQ5W5d7kXWzDPGUCOrA*Ijs+i_ zs!b)$jn6Q{?97XWo$@rh3S@7{`S{!|A9^?}sj-baXn%Iw!-qg%Z3Dah#`j@i>h+4J z$HtQeM_!D$Z?fUKs^P4>o4t*+b^FVazO9xw-HnJ3FW7kMJ4MQ$!Zt$7am2uqAE|zD zEKorl*CxwoXpyISl#5kF_>$u0sdFA_3u>K51>2|RqZbX_+0>p(yRiw%>2Yv7vwF}A zJ9QEsila=C;ekDk$qKkbW}`PVj0SS4Ps5sWu}SRmi*&}eGoa!}+q(b!2GPn{g>iy; z?=5WItm<^sT<*#R&V{UUg_L4mE)t;qFccOYEvRKGIGf)RlP@~lV{X5-yy=>Vz*$At zd~e4}PCmQXsMyzFJM(CYc{WgZK6xm!SbzHbyUdO$N0ld#4#ya7+7?^iZdxTjZyY~w zi935hi*iG5F2`|AE@>x5dm1ZZwIX$mk!yEO6jxtSn~2rBkurK;Nd&tsmr;<24A)|M zE_2f!4RHti{GMPsu4G-TE$NeDW-mwP2=6?@n4LSB?VoE4`fg}6x;rh-(7}|p@_8Th zxw!boMl@eD#Y0x$PLOwLNO zH6D|vDxW~%Xqa3*)@hdje`W*DG_E6bDcpR1kivk)!N%(puQi%nkBnRtPnduCVuq~8 z*G5TOcn^M&OYv@dT7~2;&EEUkiQ%sJE~K1!tn~wm_x-wjPHEJe>ED{iOjeaoQGk{3 z>@=ZE;ZI7JnZKVmtf_7DFe3026i+m`Zp=@3nWq}4q9C) zvS9AgihOGsKKW=vn#zKqw1UNbGy!W1*)xyubSn-wcJxJCVoz>Rm>)dqDqL;wR7J%S z&R4yYB^!q}&|ZyOWCPzge0q8i4E4A;@8C8&v3u;r7#pp;rEy$quz%;CBlKO5b)#g- zv#dXoc8|4dZ@S_LxH(1KJHno|H$rZVy&Zdhss-bFhaT?rg|GF3DTF_9c*Z{ z33J5CgMc`_aW9jWOp_kyU)_rj!BgN);nH~SAtw6pI@f5C;|RV;Rg-W?qmPbo>Of|D zTvAQtkYTj13%-lXiJC`FhEb;SZ_x89s6)@a?>gK#Sh8Z;vC@)NNoc(qa z4w@5l*S1{4p_T&BMv3gFCmXcE@5JjNl}^0uwwpurVv%&wRapo6nU3;l{pveFho}^K zOQAztoTQUW{)Nt>t`R2R={uG8W}hD6CwVH661)TbB0c&k*kjO4jlK@|Pb3Wusp95o zHTId#dvRX)e7o)@k`Yt8of-c9kJTb#yq&W1^0gu+sqngn!}Y5xvrh=NS=EWVUyIFn z{cQ8??Ci1fxUreCkMH#sYHl`>HLz0@*cw#c$uqk}2#R;rkLRvS5!*b|;&?5Vw?3O{_?)Ek2hTWopV1E005V+g>)AMVHQD z;K6)H!M#Pj*<%`=t?cC)*%=QtbI159=>tXluD` zDrxk6&Ht)%V3y5`e2QUk7q>B|qr{AchQp}-8GF#&@!MkaCU2y6)eAY2CXTfsb>i)7 z8gmKS3$2T$7S}I(CYojS7aZPmJ=u=`Rmg22sUwCVT=^Dv18iu_b?+9pN{n@0e*H4= z{f#YhOxz*++ckZKOCM-r(K=k3)k<4P>f*A>7~?(d;iSgNc}`FeCpSWKtm5$*MFpj| ziS-I&W%9=iST9mR1iYPCJ^OI?La+)7O!0Ga6acnYs1k$cJisPDU_;Pnwa?jqtyjA)xTx}2 z?0!*P^xm;bP7*s$k0FI5^$`wwDUJ)GdK@16-BXX8$>iETxA|#+s_KE=j>VlmS=~n$ zc|61kNk9{S(r$m>#>f-9ByFWr>8mAoM)##ii)9z*y@-fwBJ>vntkXy4v@gY=j=|8|UYTio8Td5-U9AdRWc;u+7|z zAJbf&v=*%|KdtmO`euxdws+^Q{3acfvZ-cSp(OHlF`87(6zA>IXZnT?>FawqJuKUI54tXS3IB(dN=5Nzf#^ zG-A#aXuxHla3)|R!5ng8OLFoo&)txSec~l1DR@ea=|p&?p>v_!(Kbuz%^!Ln4)f07 z;NCE>)qOPd&OhL?FMG(|2l7_ZixGhlCfn*`hDA>gH$FXdG{Wrlh>+b7kN+z|lbc)7 z2r)#AF&kdHWSiLgs-72}Y#B)orW>_?CO+@O718S*8aX`v{%o>t3-!G9!DD3;_#Pt( z<=1!NDK#dzIEn?f*|YJn#vjC=YoxL7IgE4kn9~(42*fQac^>m`L@8Lq__!O4Oiw&Hvsdw@9GG4yi`s!ZI z`D?-_Pf^u_A>leIZ&~r0?ME4L#x35C?dr-kEt*qC1pqD>tw7VfX|E2aG$hx;kGM zOE~=nmnso)T)Y;C*tTsE(SBxoF=R(NfC3g@lL9T-yD0-;!z%BUb+|c0PMtO(c8)3W z#Z}Hmj;m6c`fMz_Htg_topEn!Qn8iAz>6ErO}Ls!)~6)0@)T^IQjo!bLo%LM!NauL zzj#!9;yQ!i?Y%Uc$-Ju-S=6l3OeCZ88x4=7t5?P^`s-qCo*xtob1o*n>Z`Gd$6Xg! z)z6aqWl-`ZC(5m8BRB-uG1#$$`iD5<2zEwVz75u62O=Rh%D(JnH%+!@2wVs zITy%{Zr+7`TjTh2j_#%b`9}*2-e(S21Ci|FE*hLMH#uR{jni1l1zMoF@g#}C-W>dp zdJEZjd5tn+`M&pTHux`w*QZ(*uv3|JI z(325kBjg<(5g^)%Z=UMyu4G-_xRGDcT1)v=Yrq8ShY2wafGe2zc1f|Lh^yw%Nl#na zsk{Q}(&_{^*&P- zcPTgD(|bh!4YR=!>bb{p!k&?A9@e`kVLCQ6`wwQgzfLW-CMV1*qhP_en!$q57skujL#YF>(wGSxe*<4x0PAX|2 zjj56X>isizo}NZ`@b-yq+v=ZpskEu6ecy|b548ssg`b^0&3JIww|t`7S(Lw|*d>bX z6(M_5?VGW<>GwjAMt6gJJ!~5DXRqkCyjHw>8pGP-cow@OU7Lt&NN4_p*j4kJ8l%Sp zyY^PuiGl9+CC(&+yBV2`676z6Fo#Ad+2ZNOZUQty%wtB^54>-1b{ra;m)Kqu(HX=V zyO^YDLzAn(O2!OaBgCCQP0V$4!Muzo%?IpiX-2EE6U-A2 zFS5~*2SrX!UG};=EaGRyz3b_aRr!1W!=M?t6WtwS{@&FI3*o`fhI{U@Lummz1 zbLB#}Vy}F$%|6#%q6-bEu_Outmob61&@7KF&7$WvNKeD zUz|C4d4f^mc zs!p)U$=!z8>?AL44vu?OuRyC-JSe3u5tOAlXPPnLNIvGs@9Suk(Uo<9hA*4bP%G|C z*Vsh~Rx4gGn}A7xW?o2ULN0ua@4$2GyUpPa^Zxyq7sRfV$dX+<&U=YLQjqEZ%PVQA zu;=uJuRAV+$wpS`nk18fEf^9v0$$8xmCh>c9G1OZ(Hs&QyU3+J!S?F*?fbJapmMye zsPpVbRC*Y{{YU_A*+E}IbOKEKKv7asQYqp`AGJ0aIo@nyxp%ZU@P?4P826Odz87OA zUeBbYOl+~|H|!erD)ZBbU!(}5Wq&3mcEfw7xlLIkszLjsXMBci;q=10ZAq|`54l)K zET%|}&IYAy2NEB;$AthUQ+@Y2UvM8YM0=*3>`+T8!Ci>SUe#TnzvwYm{>A)sLuFT4Ozv%99aZB!+z8y5;U^~e?f^LpO znvyBmj^=(s!|p^#N>*5O{%JuY>5#<7_)4+1dX1YbpBxzO>U-Bqm+~eB(5>1fr8zs+ z8`1j~c%!(Ges6wb=Qea1Ogqe(qZfEG?;3Foy)VLhl`=+a{c88rag7HjDIH>(vi9ve zUsXCg=jVHC=fQGP!ilMiDV4P+;>CgV-McxogV`Q_2veHp0+ak(!3VX!MxgB%W1EWBQsAF}ZW(|b3E z9)HSU-~k4&L~+>iGrMdw<{l_3$_%=MjP;t1?GYSui{okWR_iX@+809i;}%K`dbd`e!&axK&Y3U?6ag6WT3+*+DH;1BI|lX zy*5yhL~s%Vzvjuv4ZI zm~HJQv)O^nQ>vA7LnEoSr}t!UzjNoJ_Sg|Ujakb#gQrr0QcfCkOPh8-T)6A)Gs0=L zut?@YI@6T66$z^0pR6eW@iXwZ%MMi@Sui65qP+9ZD_~oO-;S$C73w zq2o}kdZC8pSY2#}7hZaAN0+34lwmQ4;kC$XeDF=MJ6re`A>Yvr$#>gmMDKR_ zB~OjXF|iIb^Ba10gqn^h92CRmIc`IfQ>c=Oc`SOdNmO3t_;jzab?lvN7ScO}!wpX` z7sKR^7)plS_e)^d6_kQkox;FqyRVD>%EEN~01L~m8h$lHqrElYv-q%iV!=n{+UnzB zUM5Mrs%suc*8H(?md*OI<^`v`QnKm@HhSg#Vb1XfB-4jxcsizY$Fk1|NzP5Z|494{ zPo$~xeftU1Qa?V=5fcKVVJblz`vD2eiWf8Fo>3_t6XZ%ALRefM4ysxC`uc>;)m>Ut z8`LY-p3q5H*m}r(qwNm1sTmyNxQprcxi!hzKhQGg>zz5;y}dAt+*6Nn_i>`*ZVzXU zj_Zby8+9q~MH{yDq2aW06brheBKnH|74?G$dOFQYU=AtHZXepqilm20-5-+&DF^z&g>?O5<}_ZmDfR8&Z_ zTZIgYHR=@`;*{|{YGYjR`bzX3tKgU?M)#gw3{~F%_R<~Rg5 z!mT|Gi!!F2o{oPME7#DIB>ZwGH#VBto(8HGtCT9~4fN89sjlaTLK7<0G8^cH-9Lo& z30-$`<4n}!r1!^9(~_Uv{OXRJ|G_qkZ0pc5)qrF&E2#}t=F#%vAdrQ(<)*PWAKWY2M~c5& z*EjivHo0Qkz*8OTt$mwt-^VV>>Lq&0Yn2|91h@~2zwMoL=aNqEvzqWPd*MfyB0!pw zrLYOj?9tJWRGwuy3z3UWJ$18lGt=&FdaWgB1>w72j1m)Rsa4*0zY+O_cMGwm!ErSb zrpuQxeAsIn3t0~dcN@}`_Rd<{ylS>0FtX+0+n9l8#v?N1I8V)O zq02t1>WveJ^9yimW{ZU`bYb0?+cVoXmW_wyULT@$g@*oF& zV8~ZBeyG-6NmQ{p&-x&zV}rAkbC$6}aC_@cCdL``wY5yUhKWc$c(u23 zwrOku2Q*RR>?LH_kx-s%=oTnb_^jT~u5cer>-Hqaq*T<4+ye)~y@DMVBQlAvdEdG> zbeXeJ54hsJh|AYAJ4?uF;8wjIouN!%iUe&Sjhne#e@as6PH7dsR+9o(+GxSyyz0hO zDPr0!dn~{%Yx6ubZs`*$n_cSYQf%91`Rk@lc(79iXgH8x!A<1XFYGPt!2z1ViXW6(|=sfO{(A~OqX*ASdd>0N(nw+XCxn>-ztwtie3!u-p#ovO! zsFY8Wb@PkRP3QZDJVetcjWxlZA`2W-kpx_3merS2r}H!OZnKy z%c|l^3Bx04dPchpvO~c#+0Iew^$!AT;;gLBTltR$kBi6e!ZvJCrXfo)#zxy=_3oWf z!(RN8eJPYFMjtYGt8RCRgbi*~Dx&|`X+1e?^V~bJ>;1^qw+|{C<&PR*^ig5TMB3Ty zx^$t1u9ojzPgvOegC5lzlJTz1uH4yA7j%2nv#GXrhw8XllueAjB{J!w1M6;Yw>gaT z49}T94-&Hc2c~Dz!<()jbN3Pp@=BMJ1wDnA2l~6J<->;0>IcoT$)AZ`bS6M&s1tmp zjeb&jytQre1C_^hpS`}J3KQakq};Zk4jZ1D6(>DH@-d$z*l8hUq zR6oTO=Hr@fj22n#-bYhzwnQ>_JReTZ@Y^%;6P|))5eW*ip_{lrwckUQ4Vu`iCmip} zU3&R?M^8c(|1lgsY)B}9l-tp)$n;_7&WYZgS8{hs5t!jU7PBau0s2y@H8ck^G8m|$u?KW6vf{DP2x5nOaB^%stCj9vsJ~N~nNlah ze?~J#O#9+(_0m(XJp zIDBeSiAy((Z(-ITwL1UBW#SB96AE?k@tgNiHVt+02pMvxa7%mn`-%1TO*hV{Woay! zS2c@(W2s+`>_x|*?>)UQf1zjwODieNo!$D0%26B9tXR;Of*ErYwNYlh+d(8BBOcc^ zP3Uo*-0yJLq{&eI~!)6&!pDhFNE3aTy>Yn+PYn zckH5^5!4aK!!FKCQNb(3$tN#m3d0^J6u|s2F+l!4oUbfu0UT=V=^>zte)0Yl>i6#8 z1JVf367JXnW?4Y?p%Lsbj=|=glOu2CvE$3o-vpjG9V+!i+`Q8OAe5O_0>x!X&GIM~ zoA>lEy{l!Y4s?f*qGGod1qcSA+&uVr0^G;jj0xohsKblE2R^~|E=Hp^<2!AU#r7T? z`9z^E-=LQI@Po<#p(6b5`;pI61sMGZ%L=!HBjz5L$iCc1aOBic+|8iLgPqh8p+=`p z3*|etn;CjE*W(dPs>WGsBFAPj@aAEno>b30eB%1>iv}-wDa5S~i}&3j0PAIY(qbP0 z&XnQH`1vOAJ$PTpFYGeQVl%}iE+OwZi=D;5$e3ldV{_HRY=Hp!bS3oo$8>xY*c_1s$)MSwm3oM-<|m#0HMX_&6&>PIlqDUK7CL>wBfVKFdbqHPf< z$;8Iw1KS*<=e~+vjA-msQk#rZO+@tYu+znEJ;SNNQRj`PMK{I*VJTU}{4ng$4SVFg zUrHJ6hL!pQ%*Qt|WfMOPV~ia1?mIGwSJQkZo(0{QN_@-p9m(SE8>a+P%Oy4@es;^KG z>^OVqVZD(R{!u+rQE*B|>Jc8zcM6BWITUZmV(+9zbBee>yB=?V|8V!Mw>CTsW%tm6 z$wGT>PP>~)#T#&kk`fXVVDla9NbNV;ztRx&JC;a`9y$nIoDs#vtq{m3|Dmu@Ro+n6?2c zgTEN_Ec+E!<#VtqbTKpwVCp9|Q!Vugu&yGbqajz4=Xr%1x z%y@M^7@T4Q6Fei#8+=OU@qTdM2s#1Izkw#`eEOilh&RFD<@1R(H_`F1V_zBYXj>7(r z0`$B@oPwfjFy-y`r|Z3|(&WDCIkSg@>i zAL`r>$R#Qc21LewD$xGFRJL)D4glZn*uV7s{;hIb_q~v@hd2NoBg(%}&Jgxce6Vhv zP}jdrJ}a?D)&U3(K-+(jJQ4Bzzv-QR%XfjWj)?h7?<25ZsRRCHJb>~8!~Y^a{?^#9 zh5x^p15jRX0fqmM>tsZ`hp_(<{1N)#AHo2Y)BPL14}^Vq^e5aA_^-tQ1kUSi{~-?i zmOA&JmK6ef2>+EFfUd1p*25n8+CN=4)c3D%51=-Vz#XwgZvTHr2OxNW!eD(2{weva zhW*O+|13`@PqJ4kQ7KO*^c>F)*0%0E(^`+iNVE?Oh z0D>p~&$^<%YdPN_Y(pbI;lCaSAdVoHpQ!^-_Fy?C|1XXru>ThPf3*%kap3>U0c7ld zCjLL_2cT@h{}2C_*hBdLOb#Hh{L$_IOaJ{BzX!7MArYVOPEI=vq zWea|dE`YEP4*!IIbo@48YPth}K0UqBKwQ%9AJPFo+Ydz68UMxh`K#(22I~0Wu)pwc z@9=?RkN9t9rVmI;I|Ppd*NX$b)erm`Isl~yevTek!u~V0U>|kkE{dQ+P#-yjyRZ{Yy*-%(EX4f2;~NU zr7wu`1-_j}A)t>R5d0VZD|JA9!wUrD%eJZMOy3m?qIAHwWAeT2_ebxC;yZr``_K5V zrUMF!WR~F$9gL6HFJb?)^Fau`@S{E;^56ZaT>cAx2Vws{2nqi+bU;iT*)seG1~Y-8 z5@}#+>LV~VUIS$2ocU%O@c-5UC_Ava&Vl^<_kn-mzm^U_!~wm1$-v^`98g_r2te@w zw2%07_e3q}0w@>owS2(;n*;xaw){PHgnTKiSW`T-C=AY%J1b^v*N2x1@M`>os}`*c6Lzm{D73+Exf-sk;t{J*9H!lSnV z9bKVt+-GL`ft0++S@@4^3jbO6c@{J6e=+O;=?J)(Tp0ZaI= z;s>T@3V{86T}y2~Ds~5$6Gj7uhKrW4N9<(f^8Yp*_%F0|UDcKwDa>5Kyj%8m=i`@@&nT{I1sQeZ6_yN!Cc;!FF1g(0bh#;5V{|E zyHfunAOEQSNBkYKeE#Hp2v09x5PPVA99Y61$qp>x59)yUBt~Flv=WZ%&`==|o3MY? zoN!aK;}Z6Woz^zDFYy06z97T}WXzHG5jg(n_D{wfQ8x(tw?519U&a9>9q>K~9Vji= z1nv3^2+j{{bV1rQv7 zazaopVC83SZWKsN-Tws#5cq%14*Wm;!9NvyNS{O4e}VsJ8vyA5gddoheGQnM9frf$ z+~NTQhvLC~fYtIrtMv;sG}^AD05CRI14PB*ugQV++CSpj|3eS_iTFb?y@wYJ@cfKF zG6xVo0EEAPFh0=S@(zyO%*+6gk#+g6e9$Vf0HSXQ*$zNDAS@CG7#*wrf&<+>kzfuO z{f9ZQ68~Ri3;rYyKzSXHw`=2%@B`zMxWK-<7C2^I-3dTQ_)d5}aIKu+YIXoQFP!p$ z73>RG_<{qC&9-ae|08zbS8(7@!5`A+Zf{_KJ6P7@z&pPUKt<(K5T`|8etr_n;VFT3 z0Bu=L2ssvjY`|)E09gkh`UYz1OuxV%I#F7FZ%qy$bp3kUU#$oJ1pJ|x&dqau{6i!5 z0|NtvaEt~Ai$VYW;PQA7DHd4b022PI=>TM3Ff?K#F#0)eL?*)G;yjR*D}ds_FB%^R z*>V>T7~l$)wK$NIcMq7Gn*d>11atOgV0+*AWj<()e85^dU^QPbEu9@1`lWp+CP?`} z|F7l1@2UeJf6m!`ZTx)$NPzZ_k#HO!dmo#?_09Q!B^~gMz97O5G`G4g;f}lmfR0pz^i6@b}n(%4*#u%vbLG-I(B4ao~5sAIj%BI71_02g<3Aiq!-%-zp|aorS+_AuZyoxT3nR_y?u$R;{X5R z0J2Z;J7EvKYevq|C7Di6b_03&Iv^MBf@86tfNdV@tFhq9yzsa90pDr|5F9|{2K)L` zmfjV4XK`^B^y6^;aSr@$*dyMzt2-W9oS>~821-iJL0zzO-FR@Vet~c0z`AzevmQXs z4OUcXFMChqgY+!!HSzyhdSJbLF+!HV+crGv0FYN`ge=OX{rUN6psMCIXs@Z4*?_O* zh1c~3zQGQx1FQ89Ao+udDGY1A zTieHXOY6#fD+j)%&k&&_R?`K)8|H}jh5GaC9ijf69dLQp)_DQ5bDzfIuPI>g!Xo(3 z%ZG5)eu2N^3#`lyBXq!$9r(tg~a45zg7pVhzXH7@UbHh`Bj$fw}13mT3hH>bKrNyJ}z-9 z(9-(XnoIeGPrw|{9MIX70rd3bEtBV32UTD$7`booZ~202*@0C!kdnr^)+@o!*Ed-H zwZ6jJ`K;6{1 z^6OST?(Iv0&m}^P89=Nr0N;LxEm(^Ke=7D62YUKGwR>IuYczR)cb06VqeMCLIDXTtzy^|>)*q6;ES%&}L)B}Gq z_7MK}hl3+U@B0x^FK`1tKz>K}A=LkHp$FM{i}X0Hm# z=x8JOxA#GPj}0Fuu$nDcD?c0-Nxtec*E?BV^JHm08KSS~XX$}I9eW7>rsn5x@dgB) zSi&FTfWvDT@XGnq*Z{}RTSWgbe4G$CRwyEx8)$2fS{0tDsUC1VuogHrkYM@T5#;$p zNb^S^JCKtnyXv#nI$2w9xip^)(O2{>9Qc;_@V|gPgnxbGr`&&NI3HmD>Qi0_fxm;( zS2zHU6@0mq0x2mFIbzZfsJQV%q@ez^|J7Y_tajE>a; z=~+U5tu29+BSh{mLi8JcuRZv$U=QJ6QFRY4;^^2bfSto%_#-#~=>TLs0O^2bdf@YT zQE0r_`v6L?zsLbTW@II9^9$2JUA;3961I2w970G3!1VwcxcART}xD|zs(9QZF`58+=_dKE72V|XBW z{MSlW?s2%>3kuDb@L$yqtndX^vjeM*8;A14Z@e}Fnb~*1ab|U^Vhqh6%FUMtb%s8; zK1|QDdxFDuz~_?%h7bTBJ3_!dXov@2jv++m!ICccY!6m)U|D=2NU z%>LaRSQ#V!m)kwU&c!5Pf)ZT>b?XN>{s<00HUMD}+1IzqE6!xpeH$%g@@cG=B= z7=`_EyjSwSrS;RAYwmCM!SVlY4uF0DvOnku9wYh)R&pRRhI=*W;OA=VU6$4wd+Ut> z`h?fOHD|?Y5e>21d=n^E>2#1_K$Yo)aO23-D;(bwSk zuhao6Ij~l4Xswt4VG9r(2#;EQT_@-zTH8WD;g6IfLd-3LwqD>^QfN-j*Xo42{_Qd< zC_V?rKPlw_U}+6omjlcE!O!Cbz9lY1=76gQ=9*(op}B^>{@cG;YXTZa2CY>Bjm<@2 z`*UTGlg|UkKP%f9j{lEv0Llv?azksyg~%Kj7%YQ-=W73@rc1){U&WPe z|B&@*>_vWoC1CLawk`)&^9NUp3%|yJ-rlw68#FapAXez(0^~E$HFrE2?Y(sLu#$`=Q`k4lCamb^Nnsf(HMcj*lIgz;i3uFXupG zQ{cMzXXYrv+dm5XpN;d{&zqWR1A2Q);rRbb4y0!)u8Tc%(Laa=)$YF`ZvUBjaAdR? z?34Mpg#XXxfTQyUps)y9D}Mb?`^RV$_Wx(>As#{bGiW@;b1U?p$^i!_OmKdvIQScX zpVkpw5B*Pl`G-;V{vW`>Kb!UvR= z*@JV-myhRN5C4kl`zY-H0qmjQc_}G%hC^fjY6l$uZ{dKAJr>~ZNe%wS6F-oerUb4d z<_PBG^WbZWtcUYz*XkM!QP}?z*n{&f%`DJ>oZL_IK;1l!z>jV03Bi0TD>z?E1V~Cz z2FGAL1MzhQ*N;g6F)jq>bgaJ~$7&d_b`H|{sCK`$U;Z9n$}?3Hw0%UA%+|wA0mH1cXIM!p}i5U_zqOvg^|`-Qf7kd~lp-0?^Ur z4Yam90?n;9KvRn)SS&tot>EWc+Z@2_ui@h}+CDmi{rL{?@kQBrY#`=nDBS-c+#&3f zQt82U)P|Q~3muG&bpSyjSC-CaW*dPyxHCXtFcT1)h>ODPAH-~>&-SCov5DBg#Kh`r zd_rR`0)j-A@SmLO2Y(A_6KcHtAAmc8FI6>b`*p*^Er6dt^4jX)dT?2EsCNGc;Sc5a z*ZTOrKMOoHLj&hs>4WR6R0CxdGAQi-AnYN3d%cgv3w-z*3kY3-+Ws^7m|rFLt zk}C>N6bDcoKyd)Y0Tc&N96)gZ#Q_utP#i#U0L1|m2T&Y9aR9}EKb!-Le|Y^+eT8fH z6++mLpz#0C>Np7dLKOc0S^OdFU!d^+&*BeZe+-5He-{5^Fz^H7AB*C^e~trj5cbdq z#GV!`<0uaNXE*?1P5TM`Pyd6*m%%cR;=muz0SM#E$k-$H!Sffvg3<$P$IHKaE&;;! z5(4klw%~Qh7Q}(&cdskz{g-11VY*D`uZBP39C+_Buvmem04z|R0F@_PF3*26zaXrJ zKVt`B2>0_5*dlM|kq=-n?&0Of_$|wiu~L^GV;C(zMq7Ej{4(kYB>|KKP!d2%03`vG z1inQAM$2#d!oH?1zmCze{1|JcJzr_>_k;KIfW-(bXfO)!fAGTtgAs4~RN%)*|AD^5 zaj=AfWe6<)W*_M9yc<+EsE)^xst)DJi$>MjZdsN&{v`eBm?MOL&9M|avDAS_YgX}LqL1C z>8owvYaI9;WB}D67_5767z|vy&3kFBh^7{Yzug9)`v03{0M-3}?Oh3QoYj%mg^_`j z!4Rl8yTruV4Z%rpYE57h$~YXAUDkw+D_pq%CtHDp0+wtzQdvSO!MjC*vB8pW*Uik}T_%Y+1f!*_JICy?o#M^?v;`nvq7*%xGk+s$c)iNdLUe)$~oSfItKsAt_2Ezf209%k40~6_QzKE_Z+v%`Db74{mGwmst2Y@ z1Ao%>#623lp?MMiSc^u3n!49fE3H{{Kn?~6PoWmpvMY`Uh`R~Bkxl54F7Y3s0eK)X z<%^=LI}SSVqB!20hPr`Y^C%C@v>b2^XMbp3#GiCfJdkGl7HVBqq0V&#YNRBfwq=;a z-I*<5lmUJxX{s)x%sYEMQ*uB!S?@g(wV!!cebS@NL8$xn25_$y=~+wBYxN<04Ja&m z%q-O#|Y-D@waq|Csob2lgf2E_#mJMAMNltby0*dZ4ZS6G?-Ci`^nA z_0~z#z=*4}*S^G`yvp9G?4h}9k46LXKwjZ9;w<{6XXidTMi1;uyir^j=#(^|9*92> z;!+P7WkJn9@6PZxe&!1r5H|LXWe-vIk=zOVO>y%?OY1Jt(PhD$e}0S}P-{VMKI<6T(C#1anj>mz{||kxa)o8zBB=|c9)KK-Sqne|#RCb6 z^TfbFx1_=F@MTd@{HWj4fVi`V-sTAH9=rEWtmQ?bqUvqvfDrUpx7P!5EikNyMjoI| zP*(B0&I3b3eIm{F&$=#f+#?!wfuk%q(+QsEfe`4rTLt^gZr&CsatnSfPM)k32?xF{ zdrM0_06CyOY!|brJs2+X3tklm z4{Z=Tc3v-g>KXL_*8=K7odzT8p`A@+unTDytDj4QC$Sy`O`8VZz@6WjU-YyD_0!=Y ztm}~?&T_Yu2kHa%6jeOnR1RnpI@pEO1!=Y=3VnG`&s?nI@5(+Lj}N?sd!%Wu=;}(h zgOzL8krva$JTRgQNA1GV^fEU<%Dz3J>q!5;TPJDW)A!jUy`H`WBTKZ-VAEAk7UkT$YIPKbf5`#-%3$g{hqiq*e6lx?JX7=nJb|SgQQ*P zxE`2dL-D_-jcDtyE304C?IYTV@^ZolqyhO}!R4#_sngk5^A>77={E(44V$%V(^lyI zoi~c&(znpVO6x&=rmtS)75oDBz#M58sWmap_MnchdU5&kc~MpKx}A-z{ASK*Kt13q zoQ>lg>)9(JqwW?TZ3vJw2p^#VZ6eZucH#C-;O!=f-ud&LqO|;fpg%t+_lPxh z?<%xKXIH!^EO{3G@ta5eB?kM*84YF%chaG=YdhBb2gQeB0g?t`8v{i62oGp>;izpK z@ky|-Wj_u5ruls@T{?wY=}>-VG_YV@G|q>wU$ZaE z{=}7H-Fo03uJJ&agB)z$79h4@kHCF`W)}sD)bxLY-pr8mPXBLbSAu9b94>9-?A*u2 z(H<-4I?p8yXeV(k@HM`U<25w>L0Hp&DcGl8@xXe>f|7#`-~}lMiUuQlM9nS;M1QJq z(S6h^*c)RQHe^d%BxrNLJQq53x*Yocma#MdFK`{8f5Q6G#pTNb5=Z_@x2=#qql5#CVN-PIpYz4hV^;W2L!=Lt zXDI%HJMk|rWB=8SV$He$I~wS^zy%GeYuD&FQXkSbC@A_B?0~U-gZPhi2j7)`Lw~}Z z_~#Wa74;2K;{CM&b~KQ3p!mR`57nArj{g@Of4(@7^d0Fx+ZlN+*1oUeZ1{VkymBS{ zV9&x|{ygldpFq}wXI$PL^-E^XBH61`_3Kr9VAKWFhsHHQ`AY@|&+0U2Xxu6yuuq^5 zk$w_?n`cM7)q88kjgZe`*}GWLz)ly8*agmLFmUmNjz4{7TpMW<&KBHBgCuJZdTuw_ z;jegrYoaSYh_l?Q~mv}v##!9^rZi9Ikze4H(c?!r=(wpeaoo7yR3ZmjQHK0&5N%-v)XPFWtBgK4{L>> z{pYK3uZ}bG{*J42aCHu@^1!U-0l&@x^D=L#eAv(Gnk93ALF3O-?ys6_ng2SH1N@oI ze^zV=OS=PXUh^ft@rb?9SkyA|5`YKVfJ56|eE&pTb;t9S6c|jH8@r4*UrR z!fHY6n-P9@=D;6tAe>3o1&D{rLG1i8oqrVk6Yic%5C&^q4n4Bd@5O6P0t*)0+ahojV48cjmyC^FUr#Ih0zy$STo)t`T{4i8z;d zd|WvgZyvl!Z}r`bS5tW(%zNP+STh!b|MLNVvdGH4cZ@8%ql*+zsd2nS$CyX^WC)|X5+X%$hyo6Y?V94#4WSlkm zXn*nzE^$z@>`Oi(kEmQi#*1fUK7?Gty@2^H2RUHg)G*>Gqx5_k(%yI(W!V)Dp5z_o zx--s^xq!Q)LXju5TGZD48L_RQdQ78|19N;3VnE`hE_lBH!|x6bo>+$?P1hoiqf(B~Up(K}=0JW3~09Tx|+K2RS`m+sb#J4JEHJ6KQabolwAxiv=1MS?Ce=4NwE zF!GTWTRa!B8wW?=KMUEP<22{UQLj!HcJ28<$lOA4tS3*8uOJ|dDXDMRjQT16q~{c> z92%oON=;u1J3@=8=W{1c=ID7t&hn-d?L7ng==`;`Mj@8(%ZT;*069&+MQrQ$5hEHe zE(4F2)@am0xCd)Opq?8{S>U>0{9#Ry%$x_LjbzOGp&Y1-@Ov6|eqURAl+0xa+oTjF5u^SwWa0v1zEZFrJdAkFAZc5iI9V*g9~d|$+B>3Q*WF{6yJN(GbKx!6 z34ce|9>n1XYWXOew0tBLcg*;EQ|v-fcW9j$gnXVjQH;IUPsQ1OEjMRh;#Z(UZ?{{Y zjWuhC2kGw(x~uPO>)0y7HeN4t35>ZEDt=z&kwltqL7w>^ku!fBdw^z{Lt=`ZE9)&} zq0WS}+*)7c%QUyHMb5{qqd74ne6(TnXik*EA;!E=^qwe!%x57MuuYu5(1G{)Qs#5= z|I-c!+Te;t-n=(8|3yUXSfuAOFt^K27Dn>AB0dfgM-jKnpWfa==+YZtQ@^R_ZZL-} z&33=s^Tg3U@{vFAP{V6t_nxJCK7uhPg>#_fIVxt6VBR)=@(W*p{q|kVLniX|y2TJ; zoM;2}pKF3V-0U;hNu#RT=b$?t8Iw<7j3HMz9PcfW@Si+Y1sf?yu5Yx9lC7aKHi_{` z%v1Fh?Kw_a#p5Dj|C568@G1^^-FoB(h1=(c5r>vmi-e!~KD(oC@r&61!jfeoHSGl% z)BgTi*Enqd#PPa-)j)GuH!t@yEwQ*UWahdt1RoTCp# z{o%Kef3yng?31Fr>PN_TdkUXFgZ=Ff5W79kFW}}prDxs-yX>5#a^1lw*fnJ$JO3WP zfo~#S$}Ly|JEmQ(J30AJ%o@0jcqLhb{f3u~a9w@vJKO6DqVlB9znmLBMhGK>rccR z99LcHTtv}8-6#5vss5d3t~GV9V*FqUgBssB58i@-zSz?8pUHhgN!gDkq=BdAg5SgQ zHlA7K7p{G4G4!TI!uMvI5x-S`PG5sZ(v{hzc|~OFa7l9gb?Jg z7UQgF{TLc382G&LnhXqlj&(q{?+ipOtW~Ic{gi}-v{2_}){MKP-?zHf;$|D*2N zP109Oo{(pFfWfl=HuM6Bk^k4&^f!5S=LiGwcYo~B|1Btb4L-ETMEt%7;2Zn}X= z_pg}u1#%u@_Rhz=YrgRQ{zk~hKj?iL6b!_DDr57R!s1uuc@*nn(eJxudyq)WSP6f7 zj;#AsRPqAw9+q{&W6X1OnV^p3nG@$=s$=uH;?h^dI@DsJ4}HUC?HpzcYSXdiN^B8z2?Kax1LmTr_)W?4nc2_Cm=5D}o_bwV_pbz;+&+Y#GxFJ$p4QC9vQ{H$$q?4I8JIH&PWeJ5M?L!z>3 zD{52M;|%_S7`WI4Ut@>3i2K?8YMcfC#%p62l|CZ*xw$zBe&gSPzJXq&u_Mj-zNqwJ z#Fg!lb;O;&D04YYH2&iIU5&(bEOeWYx3!&noJdmtqk=UM+K_kYU1b=St+k1=Vxw|DH2 z&l_Qw=sRdrnc{+AcPz(Q_(s?{fo{VPk2std^B17omSZ1x4eYvQ;!wle(pECs$*y3? z%K0g5tHp9(!k7W>0oaczBm0{=9*PzQTFQA#M&G`R7f&J9F#_?)Aug8i!Z~R|pug&rIinXvsXpbePJc@YJZP*L{2W-&qO!zFn&m4ar>R+*^ z%YA@72C)Cht>^e`MfLNrd!z8o8| O3_AG^`RsI+)ZYP5w$HZ! diff --git a/src/Artemis.UI/Resources/Images/Logo/logo-512.png b/src/Artemis.UI/Resources/Images/Logo/logo-512.png deleted file mode 100644 index d1cd8ebe70ce8ac82eef0dc9d52f8e4408481c5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22804 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 L4tDo8a8CUXw*0?E diff --git a/src/Artemis.UI/Screens/Home/HomeView.xaml b/src/Artemis.UI/Screens/Home/HomeView.xaml index 78db3be5c..0842eb9dc 100644 --- a/src/Artemis.UI/Screens/Home/HomeView.xaml +++ b/src/Artemis.UI/Screens/Home/HomeView.xaml @@ -7,6 +7,7 @@ xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:home="clr-namespace:Artemis.UI.Screens.Home" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" + xmlns:svgc="http://sharpvectors.codeplex.com/svgc/" mc:Ignorable="d" d:DesignHeight="574.026" d:DesignWidth="1029.87" @@ -37,7 +38,7 @@ - + Welcome to Artemis, RGB on steroids.