1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Added dialog VMs

This commit is contained in:
Robert 2021-11-07 12:27:10 +01:00
parent 93cfc0e001
commit 8a7f8cff96
26 changed files with 626 additions and 423 deletions

View File

@ -6,8 +6,12 @@
<ApplicationIcon />
<StartupObject />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>C:\Repos\Artemis\src\Artemis.UI.Avalonia.Shared\Artemis.UI.Avalonia.Shared.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.10" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.10" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.8.3" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.10" />
<PackageReference Include="Avalonia.Xaml.Interactions" Version="0.10.10" />

View File

@ -0,0 +1,85 @@
using System;
using System.Reactive;
using System.Reactive.Disposables;
using ReactiveUI;
namespace Artemis.UI.Avalonia.Shared
{
/// <summary>
/// Represents the base class for Artemis view models
/// </summary>
public abstract class ViewModelBase : ReactiveObject
{
private string? _displayName;
/// <summary>
/// Gets or sets the display name of the view model
/// </summary>
public string? DisplayName
{
get => _displayName;
set => this.RaiseAndSetIfChanged(ref _displayName, value);
}
}
/// <summary>
/// Represents the base class for Artemis view models that are interested in the activated event
/// </summary>
public abstract class ActivatableViewModelBase : ViewModelBase, IActivatableViewModel, IDisposable
{
/// <inheritdoc />
protected ActivatableViewModelBase()
{
this.WhenActivated(disposables => Disposable.Create(Dispose).DisposeWith(disposables));
}
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
}
/// <inheritdoc />
public ViewModelActivator Activator { get; } = new();
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Represents the base class for Artemis view models used to drive dialogs
/// </summary>
public abstract class DialogViewModelBase<TResult> : ActivatableViewModelBase
{
/// <inheritdoc />
protected DialogViewModelBase()
{
Close = ReactiveCommand.Create<TResult, TResult>(t => t);
Cancel = ReactiveCommand.Create(() => { });
}
/// <summary>
/// Closes the dialog with a given result
/// </summary>
public ReactiveCommand<TResult, TResult> Close { get; }
/// <summary>
/// Closes the dialog without a result
/// </summary>
public ReactiveCommand<Unit, Unit> Cancel { get; }
}
}

View File

@ -17,6 +17,17 @@
"System.ValueTuple": "4.5.0"
}
},
"Avalonia.ReactiveUI": {
"type": "Direct",
"requested": "[0.10.10, )",
"resolved": "0.10.10",
"contentHash": "hDMPhusehGxsHpwaFQwGOgEmAuFLp9VVnFDrX6Le1+8idpfxgHYWyzqo4uVYUiEO1OC2ab0Ik9Un/utLZcvh7w==",
"dependencies": {
"Avalonia": "0.10.10",
"ReactiveUI": "13.2.10",
"System.Reactive": "5.0.0"
}
},
"Avalonia.Svg.Skia": {
"type": "Direct",
"requested": "[0.10.8.3, )",
@ -191,6 +202,14 @@
"System.Xml.XmlDocument": "4.3.0"
}
},
"DynamicData": {
"type": "Transitive",
"resolved": "7.1.1",
"contentHash": "Pc6J5bFnSxEa64PV2V67FMcLlDdpv6m+zTBKSnRN3aLon/WtWWy8kuDpHFbJlgXHtqc6Nxloj9ItuvDlvKC/8w==",
"dependencies": {
"System.Reactive": "5.0.0"
}
},
"EmbedIO": {
"type": "Transitive",
"resolved": "3.4.3",
@ -457,6 +476,17 @@
"Ninject": "3.3.3"
}
},
"ReactiveUI": {
"type": "Transitive",
"resolved": "13.2.10",
"contentHash": "fOCbEZ+RsO2Jhv6vB8VX+ZEvczYJaC95atcSG7oXohJeL/sEwbbqvv9k+tbj2l4bRSj2j5CQvhwA3HNLaxlCAg==",
"dependencies": {
"DynamicData": "7.1.1",
"Splat": "10.0.1",
"System.Reactive": "5.0.0",
"System.Runtime.Serialization.Primitives": "4.3.0"
}
},
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
"type": "Transitive",
"resolved": "4.3.0",
@ -623,6 +653,11 @@
"SkiaSharp": "2.80.2"
}
},
"Splat": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "N8BMGVuUBnVLAHSVbna/st8XiLd8ulF3BfkKUSGCPqYpDCis3ELvM+aFaZQLBUIBEcweCYVLq3HFEBqHkCKFyA=="
},
"Svg.Custom": {
"type": "Transitive",
"resolved": "0.5.8.3",
@ -1264,6 +1299,15 @@
"System.Runtime.Extensions": "4.3.0"
}
},
"System.Runtime.Serialization.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==",
"dependencies": {
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Security.AccessControl": {
"type": "Transitive",
"resolved": "5.0.0",

View File

@ -4,6 +4,9 @@
<TargetFramework>net5.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile></DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\" />
<AvaloniaResource Include="Assets\**" />
@ -11,9 +14,6 @@
</ItemGroup>
<ItemGroup>
<None Remove="Assets\Images\home-banner.png" />
<None Remove="Screens\Settings\Tabs\Plugins\Views\PluginFeatureView.xaml" />
<None Remove="Screens\Settings\Tabs\Plugins\Views\PluginSettingsView.xaml" />
<None Remove="Screens\Settings\Tabs\Plugins\Views\PluginSettingsWindowView.xaml" />
<None Remove="Screens\SurfaceEditor\Views\ListDeviceView.xaml" />
<None Remove="Screens\SurfaceEditor\Views\SurfaceDeviceView.xaml" />
</ItemGroup>
@ -50,6 +50,15 @@
<Compile Update="Screens\Device\Tabs\Views\InputMappingsTabView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Update="Screens\Plugins\Views\PluginFeatureView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Update="Screens\Plugins\Views\PluginSettingsView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Update="Screens\Plugins\Views\PluginSettingsWindowView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Update="Screens\Root\Views\SidebarCategoryView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
@ -62,7 +71,7 @@
<Compile Update="Screens\Root\Views\SidebarView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Update="Screens\Settings\Tabs\Plugins\Views\PluginsTabView.axaml.cs">
<Compile Update="Screens\Settings\Tabs\Views\PluginsTabView.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Update="Screens\Sidebar\Views\SidebarView.axaml.cs">
@ -83,18 +92,6 @@
<Content Include="Assets\Images\Logo\bow.ico" />
</ItemGroup>
<ItemGroup>
<Page Include="Screens\Settings\Tabs\Plugins\Views\PluginFeatureView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Screens\Settings\Tabs\Plugins\Views\PluginSettingsView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Screens\Settings\Tabs\Plugins\Views\PluginSettingsWindowView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Screens\SurfaceEditor\Views\ListDeviceView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<Generator>MSBuild:Compile</Generator>

View File

@ -0,0 +1,20 @@
using System;
using System.ComponentModel;
using Artemis.Core;
using Artemis.UI.Avalonia;
using Artemis.UI.Avalonia.Shared;
namespace Artemis.UI.Screens.Plugins
{
public class PluginPrerequisiteActionViewModel : ViewModelBase
{
public PluginPrerequisiteActionViewModel(PluginPrerequisiteAction action)
{
Action = action;
}
public PluginPrerequisiteAction Action { get; }
}
}

View File

@ -0,0 +1,133 @@
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<PluginPrerequisiteActionViewModel>.Collection.OneActive
{
private readonly bool _uninstall;
private bool _installing;
private bool _uninstalling;
private bool _isMet;
public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall)
{
_uninstall = uninstall;
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;
Installing = true;
try
{
await PluginPrerequisite.Install(cancellationToken);
}
finally
{
Installing = false;
IsMet = PluginPrerequisite.IsMet();
}
}
public async Task Uninstall(CancellationToken cancellationToken)
{
if (Busy)
return;
Uninstalling = true;
try
{
await PluginPrerequisite.Uninstall(cancellationToken);
}
finally
{
Uninstalling = false;
IsMet = PluginPrerequisite.IsMet();
}
}
private void PluginPrerequisiteOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(PluginPrerequisite.CurrentAction))
ActivateCurrentAction();
}
private void ActivateCurrentAction()
{
PluginPrerequisiteActionViewModel newActiveItem = Items.FirstOrDefault(i => i.Action == PluginPrerequisite.CurrentAction);
if (newActiveItem == null)
return;
ActiveItem = newActiveItem;
NotifyOfPropertyChange(nameof(ActiveStemNumber));
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnClose()
{
PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged;
base.OnClose();
}
/// <inheritdoc />
protected override void OnInitialActivate()
{
PluginPrerequisite.PropertyChanged += PluginPrerequisiteOnPropertyChanged;
// Could be slow so take it off of the UI thread
Task.Run(() => IsMet = PluginPrerequisite.IsMet());
Items.AddRange(!_uninstall
? PluginPrerequisite.InstallActions.Select(a => new PluginPrerequisiteActionViewModel(a))
: PluginPrerequisite.UninstallActions.Select(a => new PluginPrerequisiteActionViewModel(a)));
base.OnInitialActivate();
}
#endregion
}
}

View File

@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Avalonia.Shared;
using ReactiveUI;
namespace Artemis.UI.Screens.Plugins
{
public class PluginPrerequisitesInstallDialogViewModel : DialogViewModelBase<bool>
{
private PluginPrerequisiteViewModel _activePrerequisite;
private bool _canInstall;
private bool _showFailed;
private bool _showInstall = true;
private bool _showIntro = true;
private bool _showProgress;
private CancellationTokenSource? _tokenSource;
public PluginPrerequisitesInstallDialogViewModel(List<IPrerequisitesSubject> subjects, IPrerequisitesVmFactory prerequisitesVmFactory, IDialogService dialogService)
{
Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>();
foreach (IPrerequisitesSubject prerequisitesSubject in subjects)
Prerequisites.AddRange(prerequisitesSubject.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, false)));
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
pluginPrerequisiteViewModel.ConductWith(this);
}
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel ActivePrerequisite
{
get => _activePrerequisite;
set => this.RaiseAndSetIfChanged(ref _activePrerequisite, value);
}
public bool ShowProgress
{
get => _showProgress;
set => this.RaiseAndSetIfChanged(ref _showProgress, value);
}
public bool ShowIntro
{
get => _showIntro;
set => this.RaiseAndSetIfChanged(ref _showIntro, value);
}
public bool ShowFailed
{
get => _showFailed;
set => this.RaiseAndSetIfChanged(ref _showFailed, value);
}
public bool ShowInstall
{
get => _showInstall;
set => this.RaiseAndSetIfChanged(ref _showInstall, value);
}
public bool CanInstall
{
get => _canInstall;
set => this.RaiseAndSetIfChanged(ref _canInstall, value);
}
public async void Install()
{
CanInstall = false;
ShowFailed = false;
ShowIntro = false;
ShowProgress = true;
_tokenSource = new CancellationTokenSource();
try
{
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
{
pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet();
if (pluginPrerequisiteViewModel.IsMet)
continue;
ActivePrerequisite = pluginPrerequisiteViewModel;
await ActivePrerequisite.Install(_tokenSource.Token);
if (!ActivePrerequisite.IsMet)
{
CanInstall = true;
ShowFailed = true;
ShowProgress = false;
return;
}
// Wait after the task finished for the user to process what happened
if (pluginPrerequisiteViewModel != Prerequisites.Last())
await Task.Delay(1000);
}
ShowInstall = false;
}
catch (OperationCanceledException)
{
// ignored
}
finally
{
_tokenSource.Dispose();
_tokenSource = null;
}
}
public void Accept()
{
Result = true;
Close.Execute();
}
public static Task<object> Show(IDialogService dialogService, List<IPrerequisitesSubject> subjects)
{
return dialogService.ShowDialog<PluginPrerequisitesInstallDialogViewModel>(new Dictionary<string, object> {{"subjects", subjects}});
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
{
_tokenSource?.Cancel();
_tokenSource?.Dispose();
}
base.Dispose(disposing);
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnInitialActivate()
{
CanInstall = false;
Task.Run(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet()));
base.OnInitialActivate();
}
#endregion
}
}

View File

@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf;
using Stylet;
namespace Artemis.UI.Screens.Plugins
{
public class PluginPrerequisitesUninstallDialogViewModel : DialogViewModelBase
{
private readonly IDialogService _dialogService;
private readonly IPluginManagementService _pluginManagementService;
private readonly List<IPrerequisitesSubject> _subjects;
private PluginPrerequisiteViewModel _activePrerequisite;
private bool _canUninstall;
private bool _isFinished;
private CancellationTokenSource _tokenSource;
public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects, string cancelLabel, IPrerequisitesVmFactory prerequisitesVmFactory,
IDialogService dialogService, IPluginManagementService pluginManagementService)
{
_subjects = subjects;
_dialogService = dialogService;
_pluginManagementService = pluginManagementService;
CancelLabel = cancelLabel;
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>();
foreach (IPrerequisitesSubject prerequisitesSubject in subjects)
Prerequisites.AddRange(prerequisitesSubject.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, true)));
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
pluginPrerequisiteViewModel.ConductWith(this);
}
public string CancelLabel { get; }
public BindableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
public PluginPrerequisiteViewModel ActivePrerequisite
{
get => _activePrerequisite;
set => SetAndNotify(ref _activePrerequisite, value);
}
public bool CanUninstall
{
get => _canUninstall;
set => SetAndNotify(ref _canUninstall, value);
}
public bool IsFinished
{
get => _isFinished;
set => SetAndNotify(ref _isFinished, value);
}
#region Overrides of DialogViewModelBase
/// <inheritdoc />
public override void OnDialogClosed(object sender, DialogClosingEventArgs e)
{
_tokenSource?.Cancel();
base.OnDialogClosed(sender, e);
}
#endregion
public async void 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);
}
// Disable all subjects that are features if still required
foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
{
if (prerequisitesSubject is not PluginFeatureInfo featureInfo)
continue;
// 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);
}
_tokenSource = new CancellationTokenSource();
try
{
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
{
pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet();
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;
}
// 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
Session?.Close(false);
await _dialogService.ShowConfirmDialog(
"Plugin prerequisites",
"The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.",
"Confirm",
""
);
await Show(_dialogService, _subjects);
}
catch (OperationCanceledException)
{
// ignored
}
finally
{
CanUninstall = true;
_tokenSource.Dispose();
_tokenSource = null;
}
}
public void Accept()
{
Session?.Close(true);
}
public static Task<object> Show(IDialogService dialogService, List<IPrerequisitesSubject> subjects, string cancelLabel = "CANCEL")
{
return dialogService.ShowDialog<PluginPrerequisitesUninstallDialogViewModel>(new Dictionary<string, object>
{
{"subjects", subjects},
{"cancelLabel", cancelLabel},
});
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnInitialActivate()
{
CanUninstall = false;
// Could be slow so take it off of the UI thread
Task.Run(() => CanUninstall = Prerequisites.Any(p => p.PluginPrerequisite.IsMet()));
base.OnInitialActivate();
}
#endregion
}
}

View File

@ -1,106 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Tabs.Plugins.PluginFeatureView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Plugins"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:viewModels="clr-namespace:Artemis.UI.Avalonia.Screens.Settings.Tabs.Plugins.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance viewModels:PluginFeatureViewModel}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
</ResourceDictionary.MergedDictionaries>
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</ResourceDictionary>
</UserControl.Resources>
<Grid Margin="-3 -8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Icon column -->
<shared:ArtemisIcon Grid.Column="0"
Icon="{Binding FeatureInfo.ResolvedIcon}"
Width="20"
Height="20"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Visibility="{Binding LoadException, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted, FallbackValue=Collapsed}" />
<Button Grid.Column="0"
Margin="-8"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Visibility="{Binding LoadException, Converter={StaticResource NullToVisibilityConverter}, FallbackValue=Collapsed}"
Style="{StaticResource MaterialDesignIconButton}"
Foreground="#E74C4C"
ToolTip="An exception occurred while enabling this feature, click to view"
Command="{s:Action ViewLoadException}">
<materialDesign:PackIcon Kind="AlertCircle" />
</Button>
<!-- Display name column -->
<TextBlock Grid.Column="1"
Text="{Binding FeatureInfo.Name}"
Style="{StaticResource MaterialDesignTextBlock}"
VerticalAlignment="Center"
TextWrapping="Wrap"
ToolTip="{Binding FeatureInfo.Description}" />
<!-- Enable toggle column -->
<StackPanel Grid.Column="2"
HorizontalAlignment="Right"
Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay, FallbackValue=Collapsed}"
Orientation="Horizontal"
VerticalAlignment="Top"
ToolTip="This feature cannot be disabled without disabling the whole plugin"
ToolTipService.IsEnabled="{Binding FeatureInfo.AlwaysEnabled}">
<materialDesign:PackIcon Kind="ShieldHalfFull"
ToolTip="Plugin requires admin rights"
VerticalAlignment="Center"
Margin="0 0 5 0"
Visibility="{Binding ShowShield, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
<CheckBox Style="{StaticResource MaterialDesignCheckBox}"
IsChecked="{Binding IsEnabled}"
IsEnabled="{Binding CanToggleEnabled}">
Feature enabled
</CheckBox>
<materialDesign:PopupBox Style="{StaticResource MaterialDesignToolPopupBox}"
Margin="0 -4 -10 -4"
Foreground="{StaticResource MaterialDesignBody}"
IsEnabled="{Binding IsPopupEnabled}">
<StackPanel>
<Button Command="{s:Action InstallPrerequisites}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="CheckAll" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Install prerequisites</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action RemovePrerequisites}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Delete" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Remove prerequisites</TextBlock>
</StackPanel>
</Button>
</StackPanel>
</materialDesign:PopupBox>
</StackPanel>
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="7"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay, FallbackValue=Collapsed}">
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" Value="0" IsIndeterminate="True" />
</StackPanel>
</Grid>
</UserControl>

View File

@ -1,209 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Tabs.Plugins.PluginSettingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:devices="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Plugins"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors"
d:DataContext="{d:DesignInstance devices:PluginSettingsViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
</ResourceDictionary.MergedDictionaries>
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</ResourceDictionary>
</UserControl.Resources>
<materialDesign:Card Width="900">
<Grid Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*" />
<ColumnDefinition Width="5*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<shared:ArtemisIcon Icon="{Binding Plugin.Info.ResolvedIcon}"
Width="48"
Height="48"
Margin="0 5 0 0"
Grid.Row="0"
Grid.RowSpan="3"
HorizontalAlignment="Center"
VerticalAlignment="Top" />
<TextBlock Grid.Column="1" Grid.Row="0" Style="{StaticResource MaterialDesignBody2TextBlock}"
behaviors:HighlightTermBehavior.TermToBeHighlighted="{Binding Parent.SearchPluginInput}"
behaviors:HighlightTermBehavior.Text="{Binding Plugin.Info.Name}"
behaviors:HighlightTermBehavior.HighlightForeground="{StaticResource Primary600Foreground}"
behaviors:HighlightTermBehavior.HighlightBackground="{StaticResource Primary600}" />
<TextBlock Grid.Column="1"
Grid.Row="1"
Style="{StaticResource MaterialDesignBody2TextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
Text="{Binding Plugin.Info.Author}"
Visibility="{Binding Plugin.Info.Author, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}" />
<TextBlock Grid.Column="1"
Grid.Row="2"
TextWrapping="Wrap"
behaviors:HighlightTermBehavior.TermToBeHighlighted="{Binding Parent.SearchPluginInput}"
behaviors:HighlightTermBehavior.Text="{Binding Plugin.Info.Description}"
behaviors:HighlightTermBehavior.HighlightForeground="{StaticResource Primary600Foreground}"
behaviors:HighlightTermBehavior.HighlightBackground="{StaticResource Primary600}"
Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" />
</Grid>
<Grid Grid.Row="1" Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
<Button VerticalAlignment="Bottom"
Style="{StaticResource MaterialDesignRaisedButton}"
ToolTip="Open the plugins settings window"
Margin="4"
Command="{s:Action OpenSettings}">
SETTINGS
</Button>
<materialDesign:PopupBox Style="{StaticResource MaterialDesignToolPopupBox}"
Padding="2 0 2 0"
Foreground="{StaticResource MaterialDesignBody}"
IsPopupOpen="{Binding IsSettingsPopupOpen, Mode=TwoWay}">
<StackPanel>
<Button Command="{s:Action OpenPluginDirectory}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="FolderOpen" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Open plugin directory</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action Reload}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Reload" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Reload plugin</TextBlock>
</StackPanel>
</Button>
<Separator />
<Button Command="{s:Action InstallPrerequisites}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="CheckAll" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Install prerequisites</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action RemovePrerequisites}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Delete" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Remove prerequisites</TextBlock>
</StackPanel>
</Button>
<Separator />
<Button Command="{s:Action RemoveSettings}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="DatabaseRemove" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Clear plugin settings</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action Remove}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="DeleteForever" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Remove plugin</TextBlock>
</StackPanel>
</Button>
</StackPanel>
</materialDesign:PopupBox>
<Button Height="40"
Width="40"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="{Binding Plugin.Info.Website}"
Visibility="{Binding Plugin.Info.Website, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}"
Command="{s:Action OpenUri}"
CommandParameter="{Binding Plugin.Info.Website}">
<materialDesign:PackIcon Kind="Web" Width="20" Height="20" />
</Button>
<Button Height="40"
Width="40"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="{Binding Plugin.Info.Repository}"
Visibility="{Binding Plugin.Info.Repository, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}"
Command="{s:Action OpenUri}"
CommandParameter="{Binding Plugin.Info.Repository}">
<materialDesign:PackIcon Kind="Git" Width="20" Height="20" />
</Button>
</StackPanel>
<CheckBox Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}"
Style="{StaticResource MaterialDesignAccentCheckBox}" IsChecked="{Binding IsEnabled}">
<StackPanel Orientation="Horizontal">
<TextBlock>Plugin enabled</TextBlock>
<materialDesign:PackIcon Kind="ShieldHalfFull"
Margin="5 0 0 0"
ToolTip="Plugin requires admin rights"
Visibility="{Binding Plugin.Info.RequiresAdmin, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
</StackPanel>
</CheckBox>
<ProgressBar Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
Style="{StaticResource MaterialDesignCircularProgressBar}" Value="0"
IsIndeterminate="True" />
</Grid>
<Border Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{StaticResource MaterialDesignDivider}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Margin="10 10 0 5" Style="{StaticResource MaterialDesignBody2TextBlock}">Plugin features</TextBlock>
<ListBox Grid.Row="1"
MaxHeight="135"
ItemsSource="{Binding Items}"
HorizontalContentAlignment="Stretch"
VirtualizingPanel.ScrollUnit="Pixel">
<b:Interaction.Behaviors>
<shared:ScrollParentWhenAtMax />
</b:Interaction.Behaviors>
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding IsAsync=True}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Border>
</Grid>
</materialDesign:Card>
</UserControl>

View File

@ -1,43 +0,0 @@
<controls:MaterialWindow x:Class="Artemis.UI.Screens.Settings.Tabs.Plugins.PluginSettingsWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Plugins"
xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
Title="Plugin Configuration | Artemis"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
UseLayoutRounding="True"
Width="800"
Height="800"
d:DesignHeight="800"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PluginSettingsWindowViewModel}"
Icon="/Resources/Images/Logo/bow.ico">
<materialDesign:DialogHost IsTabStop="False"
Focusable="False"
Identifier="PluginSettingsDialog"
DialogTheme="Inherit">
<DockPanel>
<controls:AppBar Type="Dense" Title="{Binding ActiveItem.Plugin.Info.Name}" DockPanel.Dock="Top" Margin="-18 0 0 0" ShowShadow="False">
<controls:AppBar.AppIcon>
<shared:ArtemisIcon Icon="{Binding Plugin.Info.ResolvedIcon}"
Margin="0 5 0 0"
Width="20"
Height="20"
Grid.Row="0"
Grid.RowSpan="3"
HorizontalAlignment="Center"
VerticalAlignment="Top" />
</controls:AppBar.AppIcon>
</controls:AppBar>
<ContentControl s:View.Model="{Binding ActiveItem}" />
</DockPanel>
</materialDesign:DialogHost>
</controls:MaterialWindow>

View File

@ -1,4 +1,5 @@
using System;
using Artemis.UI.Avalonia.Shared;
using Avalonia.Controls;
using Avalonia.Controls.Templates;

View File

@ -1,49 +0,0 @@
using System;
using System.Reactive.Disposables;
using ReactiveUI;
namespace Artemis.UI.Avalonia
{
public abstract class ViewModelBase : ReactiveObject
{
private string? _displayName;
public string? DisplayName
{
get => _displayName;
set => this.RaiseAndSetIfChanged(ref _displayName, value);
}
}
public abstract class ActivatableViewModelBase : ViewModelBase, IActivatableViewModel, IDisposable
{
/// <inheritdoc />
protected ActivatableViewModelBase()
{
this.WhenActivated(disposables =>
{
Disposable.Create(Dispose).DisposeWith(disposables);
});
}
#region IDisposable
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
public ViewModelActivator Activator { get; } = new();
}
}

View File

@ -1695,6 +1695,7 @@
"dependencies": {
"Artemis.Core": "1.0.0",
"Avalonia": "0.10.10",
"Avalonia.ReactiveUI": "0.10.10",
"Avalonia.Svg.Skia": "0.10.8.3",
"Avalonia.Xaml.Behaviors": "0.10.10",
"Avalonia.Xaml.Interactions": "0.10.10",