mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Merge branch 'feature/default-entries' into development
This commit is contained in:
commit
ce4885f06d
@ -40,7 +40,11 @@ public static class Constants
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The full path to the Artemis data folder
|
/// The full path to the Artemis data folder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
#if DEBUG
|
||||||
|
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis-dev");
|
||||||
|
#else
|
||||||
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis");
|
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis");
|
||||||
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The full path to the Artemis logs folder
|
/// The full path to the Artemis logs folder
|
||||||
@ -141,6 +145,11 @@ public static class Constants
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static ReadOnlyCollection<string> StartupArguments { get; set; } = null!;
|
public static ReadOnlyCollection<string> StartupArguments { get; set; } = null!;
|
||||||
|
|
||||||
|
public static string? GetStartupRoute()
|
||||||
|
{
|
||||||
|
return StartupArguments.FirstOrDefault(a => a.StartsWith("--route=artemis://"))?.Split("--route=artemis://")[1];
|
||||||
|
}
|
||||||
|
|
||||||
internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")};
|
internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")};
|
||||||
internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};
|
internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};
|
||||||
internal static readonly JsonSerializerOptions JsonConvertSettings = new() {Converters = {new SKColorConverter(), new NumericJsonConverter()}};
|
internal static readonly JsonSerializerOptions JsonConvertSettings = new() {Converters = {new SKColorConverter(), new NumericJsonConverter()}};
|
||||||
|
|||||||
@ -11,4 +11,10 @@ public interface IPluginConfigurationDialog
|
|||||||
/// The type of view model the tab contains
|
/// The type of view model the tab contains
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Type Type { get; }
|
Type Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A value indicating whether it's mandatory to configure this plugin.
|
||||||
|
/// <remarks>If set to <see langword="true"/>, the dialog will open the first time the plugin is enabled.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
bool IsMandatory { get; }
|
||||||
}
|
}
|
||||||
@ -63,7 +63,6 @@ internal class CoreService : ICoreService
|
|||||||
_logger.Debug("Forcing plugins to use HidSharp {HidSharpVersion}", hidSharpVersion);
|
_logger.Debug("Forcing plugins to use HidSharp {HidSharpVersion}", hidSharpVersion);
|
||||||
|
|
||||||
// Initialize the services
|
// Initialize the services
|
||||||
_pluginManagementService.CopyBuiltInPlugins();
|
|
||||||
_pluginManagementService.LoadPlugins(IsElevated);
|
_pluginManagementService.LoadPlugins(IsElevated);
|
||||||
_pluginManagementService.StartHotReload();
|
_pluginManagementService.StartHotReload();
|
||||||
_renderService.Initialize();
|
_renderService.Initialize();
|
||||||
|
|||||||
@ -157,10 +157,11 @@ internal class DeviceService : IDeviceService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <param name="leftHanded"></param>
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void AutoArrangeDevices()
|
public void AutoArrangeDevices(bool leftHanded)
|
||||||
{
|
{
|
||||||
SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement();
|
SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement(leftHanded);
|
||||||
surfaceArrangement.Arrange(_devices);
|
surfaceArrangement.Arrange(_devices);
|
||||||
foreach (ArtemisDevice artemisDevice in _devices)
|
foreach (ArtemisDevice artemisDevice in _devices)
|
||||||
artemisDevice.ApplyDefaultCategories();
|
artemisDevice.ApplyDefaultCategories();
|
||||||
|
|||||||
@ -46,7 +46,8 @@ public interface IDeviceService : IArtemisService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies auto-arranging logic to the surface
|
/// Applies auto-arranging logic to the surface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void AutoArrangeDevices();
|
/// <param name="leftHanded"></param>
|
||||||
|
void AutoArrangeDevices(bool leftHanded);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apples the best available to the provided <see cref="ArtemisDevice" />
|
/// Apples the best available to the provided <see cref="ArtemisDevice" />
|
||||||
|
|||||||
@ -23,10 +23,9 @@ public interface IPluginManagementService : IArtemisService, IDisposable
|
|||||||
bool LoadingPlugins { get; }
|
bool LoadingPlugins { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Copy built-in plugins from the executable directory to the plugins directory if the version is higher
|
/// Indicates whether or not plugins are currently loaded
|
||||||
/// (higher or equal if compiled as debug)
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void CopyBuiltInPlugins();
|
bool LoadedPlugins { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads all installed plugins. If plugins already loaded this will reload them all
|
/// Loads all installed plugins. If plugins already loaded this will reload them all
|
||||||
@ -151,11 +150,6 @@ public interface IPluginManagementService : IArtemisService, IDisposable
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
DeviceProvider GetDeviceProviderByDevice(IRGBDevice device);
|
DeviceProvider GetDeviceProviderByDevice(IRGBDevice device);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when built-in plugins are being loaded
|
|
||||||
/// </summary>
|
|
||||||
event EventHandler CopyingBuildInPlugins;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when a plugin has started loading
|
/// Occurs when a plugin has started loading
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -47,113 +47,7 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
|
|
||||||
public bool LoadingPlugins { get; private set; }
|
public bool LoadingPlugins { get; private set; }
|
||||||
|
|
||||||
|
public bool LoadedPlugins { get; private set; }
|
||||||
#region Built in plugins
|
|
||||||
|
|
||||||
public void CopyBuiltInPlugins()
|
|
||||||
{
|
|
||||||
OnCopyingBuildInPlugins();
|
|
||||||
DirectoryInfo pluginDirectory = new(Constants.PluginsFolder);
|
|
||||||
|
|
||||||
if (Directory.Exists(Path.Combine(pluginDirectory.FullName, "Artemis.Plugins.Modules.Overlay-29e3ff97")))
|
|
||||||
Directory.Delete(Path.Combine(pluginDirectory.FullName, "Artemis.Plugins.Modules.Overlay-29e3ff97"), true);
|
|
||||||
if (Directory.Exists(Path.Combine(pluginDirectory.FullName, "Artemis.Plugins.DataModelExpansions.TestData-ab41d601")))
|
|
||||||
Directory.Delete(Path.Combine(pluginDirectory.FullName, "Artemis.Plugins.DataModelExpansions.TestData-ab41d601"), true);
|
|
||||||
|
|
||||||
// Iterate built-in plugins
|
|
||||||
DirectoryInfo builtInPluginDirectory = new(Path.Combine(Constants.ApplicationFolder, "Plugins"));
|
|
||||||
if (!builtInPluginDirectory.Exists)
|
|
||||||
{
|
|
||||||
_logger.Warning("No built-in plugins found at {pluginDir}, skipping CopyBuiltInPlugins", builtInPluginDirectory.FullName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
foreach (FileInfo zipFile in builtInPluginDirectory.EnumerateFiles("*.zip"))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ExtractBuiltInPlugin(zipFile, pluginDirectory);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.Error(e, "Failed to copy built-in plugin from {ZipFile}", zipFile.FullName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExtractBuiltInPlugin(FileInfo zipFile, DirectoryInfo pluginDirectory)
|
|
||||||
{
|
|
||||||
// Find the metadata file in the zip
|
|
||||||
using ZipArchive archive = ZipFile.OpenRead(zipFile.FullName);
|
|
||||||
|
|
||||||
ZipArchiveEntry? metaDataFileEntry = archive.Entries.FirstOrDefault(e => e.Name == "plugin.json");
|
|
||||||
if (metaDataFileEntry == null)
|
|
||||||
throw new ArtemisPluginException("Couldn't find a plugin.json in " + zipFile.FullName);
|
|
||||||
|
|
||||||
using StreamReader reader = new(metaDataFileEntry.Open());
|
|
||||||
PluginInfo builtInPluginInfo = CoreJson.Deserialize<PluginInfo>(reader.ReadToEnd())!;
|
|
||||||
string preferred = builtInPluginInfo.PreferredPluginDirectory;
|
|
||||||
|
|
||||||
// Find the matching plugin in the plugin folder
|
|
||||||
DirectoryInfo? match = pluginDirectory.EnumerateDirectories().FirstOrDefault(d => d.Name == preferred);
|
|
||||||
if (match == null)
|
|
||||||
{
|
|
||||||
CopyBuiltInPlugin(archive, preferred);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
string metadataFile = Path.Combine(match.FullName, "plugin.json");
|
|
||||||
if (!File.Exists(metadataFile))
|
|
||||||
{
|
|
||||||
_logger.Debug("Copying missing built-in plugin {builtInPluginInfo}", builtInPluginInfo);
|
|
||||||
CopyBuiltInPlugin(archive, preferred);
|
|
||||||
}
|
|
||||||
else if (metaDataFileEntry.LastWriteTime > File.GetLastWriteTime(metadataFile))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.Debug("Copying updated built-in plugin {builtInPluginInfo}", builtInPluginInfo);
|
|
||||||
CopyBuiltInPlugin(archive, preferred);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new ArtemisPluginException($"Failed to install built-in plugin: {e.Message}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CopyBuiltInPlugin(ZipArchive zipArchive, string targetDirectory)
|
|
||||||
{
|
|
||||||
ZipArchiveEntry metaDataFileEntry = zipArchive.Entries.First(e => e.Name == "plugin.json");
|
|
||||||
DirectoryInfo pluginDirectory = new(Path.Combine(Constants.PluginsFolder, targetDirectory));
|
|
||||||
bool createLockFile = File.Exists(Path.Combine(pluginDirectory.FullName, "artemis.lock"));
|
|
||||||
|
|
||||||
// Remove the old directory if it exists
|
|
||||||
if (Directory.Exists(pluginDirectory.FullName))
|
|
||||||
pluginDirectory.Delete(true);
|
|
||||||
|
|
||||||
// Extract everything in the same archive directory to the unique plugin directory
|
|
||||||
Utilities.CreateAccessibleDirectory(pluginDirectory.FullName);
|
|
||||||
string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, "");
|
|
||||||
foreach (ZipArchiveEntry zipArchiveEntry in zipArchive.Entries)
|
|
||||||
{
|
|
||||||
if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory) && !zipArchiveEntry.FullName.EndsWith("/"))
|
|
||||||
{
|
|
||||||
string target = Path.Combine(pluginDirectory.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length));
|
|
||||||
// Create folders
|
|
||||||
Utilities.CreateAccessibleDirectory(Path.GetDirectoryName(target)!);
|
|
||||||
// Extract files
|
|
||||||
zipArchiveEntry.ExtractToFile(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (createLockFile)
|
|
||||||
File.Create(Path.Combine(pluginDirectory.FullName, "artemis.lock")).Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public List<Plugin> GetAllPlugins()
|
public List<Plugin> GetAllPlugins()
|
||||||
{
|
{
|
||||||
@ -328,8 +222,10 @@ internal class PluginManagementService : IPluginManagementService
|
|||||||
// ReSharper restore InconsistentlySynchronizedField
|
// ReSharper restore InconsistentlySynchronizedField
|
||||||
|
|
||||||
LoadingPlugins = false;
|
LoadingPlugins = false;
|
||||||
|
LoadedPlugins = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void UnloadPlugins()
|
public void UnloadPlugins()
|
||||||
{
|
{
|
||||||
// Unload all plugins
|
// Unload all plugins
|
||||||
|
|||||||
@ -48,22 +48,42 @@ internal class SurfaceArrangement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static SurfaceArrangement GetDefaultArrangement()
|
internal static SurfaceArrangement GetDefaultArrangement(bool leftHanded)
|
||||||
{
|
{
|
||||||
SurfaceArrangement arrangement = new();
|
SurfaceArrangement arrangement = new();
|
||||||
|
|
||||||
SurfaceArrangementType keypad = arrangement.AddType(RGBDeviceType.Keypad, 1);
|
SurfaceArrangementType keyboard, keypad, mousepad, mouse;
|
||||||
|
if (leftHanded)
|
||||||
|
{
|
||||||
|
mousepad = arrangement.AddType(RGBDeviceType.Mousepad, 1);
|
||||||
|
mousepad.AddConfiguration(new SurfaceArrangementConfiguration(null, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 10));
|
||||||
|
|
||||||
|
mouse = arrangement.AddType(RGBDeviceType.Mouse, 2);
|
||||||
|
mouse.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
|
||||||
|
mouse.AddConfiguration(new SurfaceArrangementConfiguration(null, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Center, 10));
|
||||||
|
|
||||||
|
keyboard = arrangement.AddType(RGBDeviceType.Keyboard, 1);
|
||||||
|
keyboard.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 10));
|
||||||
|
keyboard.AddConfiguration(new SurfaceArrangementConfiguration(mouse, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 100));
|
||||||
|
|
||||||
|
keypad = arrangement.AddType(RGBDeviceType.Keypad, 1);
|
||||||
|
keypad.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Equal, VerticalArrangementPosition.Equal, 20));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
keypad = arrangement.AddType(RGBDeviceType.Keypad, 1);
|
||||||
keypad.AddConfiguration(new SurfaceArrangementConfiguration(null, HorizontalArrangementPosition.Equal, VerticalArrangementPosition.Equal, 20));
|
keypad.AddConfiguration(new SurfaceArrangementConfiguration(null, HorizontalArrangementPosition.Equal, VerticalArrangementPosition.Equal, 20));
|
||||||
|
|
||||||
SurfaceArrangementType keyboard = arrangement.AddType(RGBDeviceType.Keyboard, 1);
|
keyboard = arrangement.AddType(RGBDeviceType.Keyboard, 1);
|
||||||
keyboard.AddConfiguration(new SurfaceArrangementConfiguration(keypad, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 20));
|
keyboard.AddConfiguration(new SurfaceArrangementConfiguration(keypad, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 20));
|
||||||
|
|
||||||
SurfaceArrangementType mousepad = arrangement.AddType(RGBDeviceType.Mousepad, 1);
|
mousepad = arrangement.AddType(RGBDeviceType.Mousepad, 1);
|
||||||
mousepad.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 10));
|
mousepad.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 10));
|
||||||
|
|
||||||
SurfaceArrangementType mouse = arrangement.AddType(RGBDeviceType.Mouse, 2);
|
mouse = arrangement.AddType(RGBDeviceType.Mouse, 2);
|
||||||
mouse.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
|
mouse.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
|
||||||
mouse.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Center, 100));
|
mouse.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Center, 100));
|
||||||
|
}
|
||||||
|
|
||||||
SurfaceArrangementType headset = arrangement.AddType(RGBDeviceType.Headset, 1);
|
SurfaceArrangementType headset = arrangement.AddType(RGBDeviceType.Headset, 1);
|
||||||
headset.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Bottom, 100));
|
headset.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Bottom, 100));
|
||||||
|
|||||||
@ -32,7 +32,7 @@ internal class SurfaceArrangementConfiguration
|
|||||||
public int MarginBottom { get; }
|
public int MarginBottom { get; }
|
||||||
public SurfaceArrangement SurfaceArrangement { get; set; }
|
public SurfaceArrangement SurfaceArrangement { get; set; }
|
||||||
|
|
||||||
public bool Apply(List<ArtemisDevice> devices)
|
public bool Apply(List<ArtemisDevice> devicesToArrange, List<ArtemisDevice> devices)
|
||||||
{
|
{
|
||||||
if (Anchor != null && !Anchor.HasDevices(devices))
|
if (Anchor != null && !Anchor.HasDevices(devices))
|
||||||
return false;
|
return false;
|
||||||
@ -42,10 +42,10 @@ internal class SurfaceArrangementConfiguration
|
|||||||
new SurfaceArrangementType(SurfaceArrangement, RGBDeviceType.All, 1).GetEdge(HorizontalPosition, VerticalPosition);
|
new SurfaceArrangementType(SurfaceArrangement, RGBDeviceType.All, 1).GetEdge(HorizontalPosition, VerticalPosition);
|
||||||
|
|
||||||
// Stack multiple devices of the same type vertically if they are wider than they are tall
|
// Stack multiple devices of the same type vertically if they are wider than they are tall
|
||||||
bool stackVertically = devices.Average(d => d.RgbDevice.Size.Width) >= devices.Average(d => d.RgbDevice.Size.Height);
|
bool stackVertically = devicesToArrange.Average(d => d.RgbDevice.Size.Width) >= devicesToArrange.Average(d => d.RgbDevice.Size.Height);
|
||||||
|
|
||||||
ArtemisDevice? previous = null;
|
ArtemisDevice? previous = null;
|
||||||
foreach (ArtemisDevice artemisDevice in devices)
|
foreach (ArtemisDevice artemisDevice in devicesToArrange)
|
||||||
{
|
{
|
||||||
if (previous != null)
|
if (previous != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -28,18 +28,18 @@ internal class SurfaceArrangementType
|
|||||||
|
|
||||||
public void Arrange(List<ArtemisDevice> devices)
|
public void Arrange(List<ArtemisDevice> devices)
|
||||||
{
|
{
|
||||||
devices = devices.Where(d => d.DeviceType == DeviceType).ToList();
|
List<ArtemisDevice> devicesToArrange = devices.Where(d => d.DeviceType == DeviceType).ToList();
|
||||||
if (!devices.Any())
|
if (!devicesToArrange.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
AppliedConfiguration = null;
|
AppliedConfiguration = null;
|
||||||
foreach (SurfaceArrangementConfiguration configuration in Configurations)
|
foreach (SurfaceArrangementConfiguration configuration in Configurations)
|
||||||
{
|
{
|
||||||
bool applied = configuration.Apply(devices);
|
bool applied = configuration.Apply(devicesToArrange, devices);
|
||||||
if (applied)
|
if (applied)
|
||||||
{
|
{
|
||||||
AppliedConfiguration = configuration;
|
AppliedConfiguration = configuration;
|
||||||
foreach (ArtemisDevice artemisDevice in devices)
|
foreach (ArtemisDevice artemisDevice in devicesToArrange)
|
||||||
artemisDevice.ZIndex = ZIndex;
|
artemisDevice.ZIndex = ZIndex;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ internal class SurfaceArrangementType
|
|||||||
VerticalArrangementPosition.Equal,
|
VerticalArrangementPosition.Equal,
|
||||||
10
|
10
|
||||||
) {SurfaceArrangement = SurfaceArrangement};
|
) {SurfaceArrangement = SurfaceArrangement};
|
||||||
fallback.Apply(devices);
|
fallback.Apply(devicesToArrange, devices);
|
||||||
AppliedConfiguration = fallback;
|
AppliedConfiguration = fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Controls.Documents;
|
using Avalonia.Controls.Documents;
|
||||||
using Avalonia.Layout;
|
using Avalonia.Layout;
|
||||||
using Avalonia.LogicalTree;
|
using Avalonia.LogicalTree;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
@ -43,7 +42,7 @@ public partial class ProfileConfigurationIcon : UserControl, IDisposable
|
|||||||
if (ConfigurationIcon.IconType == ProfileConfigurationIconType.MaterialIcon)
|
if (ConfigurationIcon.IconType == ProfileConfigurationIconType.MaterialIcon)
|
||||||
{
|
{
|
||||||
Content = Enum.TryParse(ConfigurationIcon.IconName, true, out MaterialIconKind parsedIcon)
|
Content = Enum.TryParse(ConfigurationIcon.IconName, true, out MaterialIconKind parsedIcon)
|
||||||
? new MaterialIcon {Kind = parsedIcon!}
|
? new MaterialIcon {Kind = parsedIcon}
|
||||||
: new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
: new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
||||||
}
|
}
|
||||||
else if (ConfigurationIcon.IconBytes != null)
|
else if (ConfigurationIcon.IconBytes != null)
|
||||||
@ -65,19 +64,28 @@ public partial class ProfileConfigurationIcon : UserControl, IDisposable
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
_stream = new MemoryStream(ConfigurationIcon.IconBytes);
|
_stream = new MemoryStream(ConfigurationIcon.IconBytes);
|
||||||
if (!ConfigurationIcon.Fill)
|
Border border = new()
|
||||||
{
|
{
|
||||||
Content = new Image {Source = new Bitmap(_stream)};
|
CornerRadius = CornerRadius,
|
||||||
return;
|
ClipToBounds = true,
|
||||||
|
VerticalAlignment = VerticalAlignment.Stretch,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Stretch
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ConfigurationIcon.Fill)
|
||||||
|
{
|
||||||
|
// Fill mode: use Foreground as Background and the bitmap as opacity mask
|
||||||
|
border.Background = TextElement.GetForeground(this);
|
||||||
|
border.OpacityMask = new ImageBrush(new Bitmap(_stream));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Non-fill mode: place the image inside the rounded border
|
||||||
|
border.Child = new Image { Source = new Bitmap(_stream) };
|
||||||
}
|
}
|
||||||
|
|
||||||
Content = new Border
|
Content = border;
|
||||||
{
|
|
||||||
Background = TextElement.GetForeground(this),
|
|
||||||
VerticalAlignment = VerticalAlignment.Stretch,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
|
||||||
OpacityMask = new ImageBrush(new Bitmap(_stream))
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
|
||||||
xmlns:tagsInput="clr-namespace:Artemis.UI.Shared.TagsInput"
|
xmlns:tagsInput="clr-namespace:Artemis.UI.Shared.TagsInput"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
x:CompileBindings="True">
|
x:CompileBindings="True">
|
||||||
|
|||||||
@ -6,6 +6,22 @@ namespace Artemis.UI.Shared;
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public class PluginConfigurationDialog<T> : PluginConfigurationDialog where T : PluginConfigurationViewModel
|
public class PluginConfigurationDialog<T> : PluginConfigurationDialog where T : PluginConfigurationViewModel
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="PluginConfigurationDialog{T}"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public PluginConfigurationDialog()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="PluginConfigurationDialog{T}"/> class with the specified <paramref name="isMandatory"/> flag.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isMandatory">A value indicating whether the configuration dialog is mandatory.</param>
|
||||||
|
public PluginConfigurationDialog(bool isMandatory)
|
||||||
|
{
|
||||||
|
IsMandatory = isMandatory;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override Type Type => typeof(T);
|
public override Type Type => typeof(T);
|
||||||
}
|
}
|
||||||
@ -17,4 +33,7 @@ public abstract class PluginConfigurationDialog : IPluginConfigurationDialog
|
|||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public abstract Type Type { get; }
|
public abstract Type Type { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsMandatory { get; protected set; }
|
||||||
}
|
}
|
||||||
@ -103,7 +103,9 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
|
|||||||
{
|
{
|
||||||
if (_root == null)
|
if (_root == null)
|
||||||
throw new ArtemisRoutingException("Cannot navigate without a root having been set");
|
throw new ArtemisRoutingException("Cannot navigate without a root having been set");
|
||||||
if (PathEquals(path, options) || (_currentNavigation != null && _currentNavigation.PathEquals(path, options)))
|
|
||||||
|
// If navigating to the same path, don't do anything
|
||||||
|
if ((_currentNavigation == null && PathEquals(path, options)) || (_currentNavigation != null && _currentNavigation.PathEquals(path, options)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string? previousPath = _currentRouteSubject.Value;
|
string? previousPath = _currentRouteSubject.Value;
|
||||||
@ -128,12 +130,8 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
|
|||||||
await navigation.Navigate(args);
|
await navigation.Navigate(args);
|
||||||
|
|
||||||
// If it was cancelled before completion, don't add it to history or update the current path
|
// If it was cancelled before completion, don't add it to history or update the current path
|
||||||
// Do reload the current path because it may have been partially navigated away from
|
|
||||||
if (navigation.Cancelled)
|
if (navigation.Cancelled)
|
||||||
{
|
|
||||||
await Reload();
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (options.AddToHistory && previousPath != null)
|
if (options.AddToHistory && previousPath != null)
|
||||||
{
|
{
|
||||||
@ -251,8 +249,14 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
|
|||||||
if (_previousWindowRoute != null && _currentRouteSubject.Value == "blank")
|
if (_previousWindowRoute != null && _currentRouteSubject.Value == "blank")
|
||||||
Dispatcher.UIThread.InvokeAsync(async () => await Navigate(_previousWindowRoute, new RouterNavigationOptions {AddToHistory = false, EnableLogging = false}));
|
Dispatcher.UIThread.InvokeAsync(async () => await Navigate(_previousWindowRoute, new RouterNavigationOptions {AddToHistory = false, EnableLogging = false}));
|
||||||
else if (_currentRouteSubject.Value == null || _currentRouteSubject.Value == "blank")
|
else if (_currentRouteSubject.Value == null || _currentRouteSubject.Value == "blank")
|
||||||
|
{
|
||||||
|
string? startupRoute = Constants.GetStartupRoute();
|
||||||
|
if (startupRoute != null)
|
||||||
|
Dispatcher.UIThread.InvokeAsync(async () => await Navigate(startupRoute, new RouterNavigationOptions {AddToHistory = false, EnableLogging = true}));
|
||||||
|
else
|
||||||
Dispatcher.UIThread.InvokeAsync(async () => await Navigate("home", new RouterNavigationOptions {AddToHistory = false, EnableLogging = true}));
|
Dispatcher.UIThread.InvokeAsync(async () => await Navigate("home", new RouterNavigationOptions {AddToHistory = false, EnableLogging = true}));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void MainWindowServiceOnMainWindowClosed(object? sender, EventArgs e)
|
private void MainWindowServiceOnMainWindowClosed(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
|||||||
@ -185,12 +185,15 @@ internal class ProfileEditorService : IProfileEditorService
|
|||||||
{
|
{
|
||||||
// Activate the profile if one was provided
|
// Activate the profile if one was provided
|
||||||
if (profileConfiguration != null)
|
if (profileConfiguration != null)
|
||||||
|
{
|
||||||
|
_profileService.FocusProfile = profileConfiguration;
|
||||||
_profileService.ActivateProfile(profileConfiguration);
|
_profileService.ActivateProfile(profileConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
// If there is no profile configuration or module, deliberately set the override to null
|
// If there is no profile configuration or module, deliberately set the override to null
|
||||||
_moduleService.SetActivationOverride(profileConfiguration?.Module);
|
_moduleService.SetActivationOverride(profileConfiguration?.Module);
|
||||||
});
|
});
|
||||||
|
|
||||||
_profileService.FocusProfile = profileConfiguration;
|
|
||||||
_profileConfigurationSubject.OnNext(profileConfiguration);
|
_profileConfigurationSubject.OnNext(profileConfiguration);
|
||||||
|
|
||||||
ChangeTime(TimeSpan.Zero);
|
ChangeTime(TimeSpan.Zero);
|
||||||
|
|||||||
@ -8,6 +8,7 @@ using Avalonia.Controls.ApplicationLifetimes;
|
|||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using DryIoc;
|
using DryIoc;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Services;
|
namespace Artemis.UI.Shared.Services;
|
||||||
|
|
||||||
@ -94,6 +95,7 @@ internal class WindowService : IWindowService
|
|||||||
.WithTitle(title)
|
.WithTitle(title)
|
||||||
.WithContent(message)
|
.WithContent(message)
|
||||||
.HavingPrimaryButton(b => b.WithText(confirm))
|
.HavingPrimaryButton(b => b.WithText(confirm))
|
||||||
|
.WithDefaultButton(ContentDialogButton.Primary)
|
||||||
.WithCloseButtonText(cancel)
|
.WithCloseButtonText(cancel)
|
||||||
.ShowAsync();
|
.ShowAsync();
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
<Styles xmlns="https://github.com/avaloniaui"
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia">
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia">
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
<HyperlinkButton Grid.Column="0" Classes="icon-button icon-button-small broken-state-button" Margin="50">
|
<HyperlinkButton Grid.Column="0" Classes="icon-button icon-button-small broken-state-button" Margin="50">
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
<Styles xmlns="https://github.com/avaloniaui"
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia">
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
|
|
||||||
<!-- Preview -->
|
<!-- Preview -->
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
<Border Padding="20">
|
<Border Padding="20">
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:dataModel="clr-namespace:Artemis.UI.Shared.DataModelVisualization.Shared"
|
xmlns:dataModel="clr-namespace:Artemis.UI.Shared.DataModelVisualization.Shared"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.DataModelPicker">
|
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.DataModelPicker">
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
<dataModelPicker:DataModelPicker />
|
<dataModelPicker:DataModelPicker />
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
<Styles xmlns="https://github.com/avaloniaui"
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.DataModelPicker"
|
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.DataModelPicker"
|
||||||
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker">
|
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker">
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
|
|||||||
@ -16,6 +16,7 @@ using Avalonia.Controls.ApplicationLifetimes;
|
|||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using DryIoc;
|
using DryIoc;
|
||||||
|
using HotAvalonia;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Windows;
|
namespace Artemis.UI.Windows;
|
||||||
@ -40,6 +41,7 @@ public class App : Application
|
|||||||
LegacyMigrationService.MigrateToSqlite(_container);
|
LegacyMigrationService.MigrateToSqlite(_container);
|
||||||
|
|
||||||
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
|
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
|
||||||
|
this.EnableHotReload();
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +91,7 @@ public class App : Application
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
CancellationTokenSource cts = new();
|
CancellationTokenSource cts = new();
|
||||||
cts.CancelAfter(2000);
|
cts.CancelAfter(5000);
|
||||||
|
|
||||||
HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground") {Content = new StringContent(route ?? "")}, cts.Token);
|
HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground") {Content = new StringContent(route ?? "")}, cts.Token);
|
||||||
httpResponseMessage.EnsureSuccessStatusCode();
|
httpResponseMessage.EnsureSuccessStatusCode();
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
<AssemblyTitle>Artemis</AssemblyTitle>
|
<AssemblyTitle>Artemis</AssemblyTitle>
|
||||||
<ApplicationIcon>..\Artemis.UI\Assets\Images\Logo\application.ico</ApplicationIcon>
|
<ApplicationIcon>..\Artemis.UI\Assets\Images\Logo\application.ico</ApplicationIcon>
|
||||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||||
|
<HotAvaloniaAutoEnable>false</HotAvaloniaAutoEnable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
|
|||||||
@ -2,6 +2,7 @@ using System;
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Storage;
|
using Artemis.Storage;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Logging;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using DryIoc;
|
using DryIoc;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
@ -33,10 +34,7 @@ internal class Program
|
|||||||
// Avalonia configuration, don't remove; also used by visual designer.
|
// Avalonia configuration, don't remove; also used by visual designer.
|
||||||
public static AppBuilder BuildAvaloniaApp()
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
{
|
{
|
||||||
return AppBuilder.Configure<App>()
|
return AppBuilder.Configure<App>().UsePlatformDetect().LogToTrace().UseReactiveUI();
|
||||||
.UsePlatformDetect()
|
|
||||||
.LogToTrace()
|
|
||||||
.UseReactiveUI();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CreateLogger(IContainer container)
|
public static void CreateLogger(IContainer container)
|
||||||
|
|||||||
@ -37,38 +37,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Screens\Workshop\Plugin\Dialogs\DeviceProviderPickerDialogView.axaml.cs">
|
<Compile Update="Screens\StartupWizard\Steps\WorkshopUnreachableStepView.axaml.cs">
|
||||||
<DependentUpon>DeviceProviderPickerDialogView.axaml</DependentUpon>
|
<DependentUpon>WorkshopUnreachableStepView.axaml</DependentUpon>
|
||||||
<SubType>Code</SubType>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Screens\Workshop\Plugin\Dialogs\DeviceSelectionDialogView.axaml.cs">
|
|
||||||
<DependentUpon>DeviceSelectionDialogView.axaml</DependentUpon>
|
|
||||||
<SubType>Code</SubType>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Screens\Workshop\Layout\LayoutListView.axaml.cs">
|
|
||||||
<DependentUpon>LayoutListView.axaml</DependentUpon>
|
|
||||||
<SubType>Code</SubType>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Screens\Workshop\Plugins\PluginListView.axaml.cs">
|
|
||||||
<DependentUpon>LayoutListView.axaml</DependentUpon>
|
|
||||||
<SubType>Code</SubType>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Screens\Workshop\Profile\ProfileListView.axaml.cs">
|
|
||||||
<DependentUpon>LayoutListView.axaml</DependentUpon>
|
|
||||||
<SubType>Code</SubType>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Screens\Workshop\EntryReleases\EntryReleasesView.axaml.cs">
|
|
||||||
<DependentUpon>EntryReleasesView.axaml</DependentUpon>
|
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<UpToDateCheckInput Remove="Screens\Workshop\Entries\Tabs\PluginListView.axaml" />
|
<Folder Include="Screens\Profiles\" />
|
||||||
<UpToDateCheckInput Remove="Screens\Workshop\Entries\Tabs\ProfileListView.axaml" />
|
|
||||||
<UpToDateCheckInput Remove="Screens\Workshop\Plugins\Dialogs\PluginDialogView.axaml" />
|
|
||||||
<UpToDateCheckInput Remove="Screens\Scripting\Dialogs\ScriptConfigurationCreateView.axaml" />
|
|
||||||
<UpToDateCheckInput Remove="Screens\Scripting\Dialogs\ScriptConfigurationEditView.axaml" />
|
|
||||||
<UpToDateCheckInput Remove="Screens\Scripting\ScriptsDialogView.axaml" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@ -1,5 +1,4 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib"
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cdebugger_005Ctabs/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cdebugger_005Ctabs/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cdevice_005Ctabs/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cdevice_005Ctabs/@EntryIndexedValue">True</s:Boolean>
|
||||||
@ -7,6 +6,8 @@
|
|||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cprofileeditor_005Cpanels/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cprofileeditor_005Cpanels/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cprofileeditor_005Cpanels_005Cproperties_005Ctimeline_005Ckeyframes/@EntryIndexedValue">False</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cprofileeditor_005Cpanels_005Cproperties_005Ctimeline_005Ckeyframes/@EntryIndexedValue">False</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cprofileeditor_005Ctools/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cprofileeditor_005Ctools/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cprofile_005Cprofileeditor_005Cpanels_005Cproperties_005Ctimeline_005Ckeyframes/@EntryIndexedValue">False</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Cprofile_005Cprofileeditor_005Cpanels/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Csettings_005Ctabs/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Csettings_005Ctabs/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Csidebar_005Ccontentdialogs/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Csidebar_005Ccontentdialogs/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Csidebar_005Cdialogs/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=screens_005Csidebar_005Cdialogs/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
1
src/Artemis.UI/Assets/Animations/nyan.json
Normal file
1
src/Artemis.UI/Assets/Animations/nyan.json
Normal file
File diff suppressed because one or more lines are too long
@ -4,8 +4,6 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
|
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
|
||||||
xmlns:fa="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:input="clr-namespace:System.Windows.Input;assembly=System.ObjectModel"
|
|
||||||
xmlns:ui="clr-namespace:Artemis.UI"
|
xmlns:ui="clr-namespace:Artemis.UI"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Controls.SplitMarkdownEditor">
|
x:Class="Artemis.UI.Controls.SplitMarkdownEditor">
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||||
|
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
|||||||
using Artemis.UI.Shared.Services.PropertyInput;
|
using Artemis.UI.Shared.Services.PropertyInput;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
using LayerBrushPresetViewModel = Artemis.UI.Screens.ProfileEditor.Properties.Tree.Dialogs.LayerBrushPresetViewModel;
|
||||||
|
|
||||||
namespace Artemis.UI.DefaultTypes.PropertyInput;
|
namespace Artemis.UI.DefaultTypes.PropertyInput;
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
@ -8,23 +7,25 @@ namespace Artemis.UI.Extensions;
|
|||||||
|
|
||||||
public class BitmapExtensions
|
public class BitmapExtensions
|
||||||
{
|
{
|
||||||
public static Bitmap LoadAndResize(string file, int size)
|
public static Bitmap LoadAndResize(string file, int size, bool fit)
|
||||||
{
|
{
|
||||||
using SKBitmap source = SKBitmap.Decode(file);
|
using SKBitmap source = SKBitmap.Decode(file);
|
||||||
return Resize(source, size);
|
return Resize(source, size, fit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bitmap LoadAndResize(Stream stream, int size)
|
public static Bitmap LoadAndResize(Stream stream, int size, bool fit)
|
||||||
{
|
{
|
||||||
stream.Seek(0, SeekOrigin.Begin);
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
using MemoryStream copy = new();
|
using MemoryStream copy = new();
|
||||||
stream.CopyTo(copy);
|
stream.CopyTo(copy);
|
||||||
copy.Seek(0, SeekOrigin.Begin);
|
copy.Seek(0, SeekOrigin.Begin);
|
||||||
using SKBitmap source = SKBitmap.Decode(copy);
|
using SKBitmap source = SKBitmap.Decode(copy);
|
||||||
return Resize(source, size);
|
return Resize(source, size, fit);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Bitmap Resize(SKBitmap source, int size)
|
private static Bitmap Resize(SKBitmap source, int size, bool fit)
|
||||||
|
{
|
||||||
|
if (!fit)
|
||||||
{
|
{
|
||||||
// Get smaller dimension.
|
// Get smaller dimension.
|
||||||
int minDim = Math.Min(source.Width, source.Height);
|
int minDim = Math.Min(source.Width, source.Height);
|
||||||
@ -43,6 +44,38 @@ public class BitmapExtensions
|
|||||||
// Resize to the desired size after cropping.
|
// Resize to the desired size after cropping.
|
||||||
using SKBitmap resizedBitmap = croppedBitmap.Resize(new SKImageInfo(size, size), SKFilterQuality.High);
|
using SKBitmap resizedBitmap = croppedBitmap.Resize(new SKImageInfo(size, size), SKFilterQuality.High);
|
||||||
|
|
||||||
return new Bitmap(resizedBitmap.Encode(SKEncodedImageFormat.Png, 100).AsStream());
|
// Encode via SKImage for compatibility
|
||||||
|
using SKImage image = SKImage.FromBitmap(resizedBitmap);
|
||||||
|
using SKData data = image.Encode(SKEncodedImageFormat.Png, 100);
|
||||||
|
return new Bitmap(data.AsStream());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fit the image inside a size x size square without cropping.
|
||||||
|
// Compute scale based on the larger dimension.
|
||||||
|
float scale = (float)size / Math.Max(source.Width, source.Height);
|
||||||
|
int targetW = Math.Max(1, (int)Math.Floor(source.Width * scale));
|
||||||
|
int targetH = Math.Max(1, (int)Math.Floor(source.Height * scale));
|
||||||
|
|
||||||
|
// Resize maintaining aspect ratio.
|
||||||
|
using SKBitmap resizedAspect = source.Resize(new SKImageInfo(targetW, targetH), SKFilterQuality.High);
|
||||||
|
|
||||||
|
// Create final square canvas and draw the fitted image centered.
|
||||||
|
using SKBitmap finalBitmap = new(size, size);
|
||||||
|
using (SKCanvas canvas = new(finalBitmap))
|
||||||
|
{
|
||||||
|
// Clear to transparent.
|
||||||
|
canvas.Clear(SKColors.Transparent);
|
||||||
|
|
||||||
|
int offsetX = (size - targetW) / 2;
|
||||||
|
int offsetY = (size - targetH) / 2;
|
||||||
|
canvas.DrawBitmap(resizedAspect, new SKPoint(offsetX, offsetY));
|
||||||
|
canvas.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
using SKImage image = SKImage.FromBitmap(finalBitmap);
|
||||||
|
using SKData data = image.Encode(SKEncodedImageFormat.Png, 100);
|
||||||
|
return new Bitmap(data.AsStream());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -15,7 +15,6 @@ using Artemis.UI.Screens.Workshop.Library.Tabs;
|
|||||||
using Artemis.UI.Screens.Workshop.Plugins;
|
using Artemis.UI.Screens.Workshop.Plugins;
|
||||||
using Artemis.UI.Screens.Workshop.Profile;
|
using Artemis.UI.Screens.Workshop.Profile;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
using PluginDetailsViewModel = Artemis.UI.Screens.Workshop.Plugins.PluginDetailsViewModel;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Routing
|
namespace Artemis.UI.Routing
|
||||||
{
|
{
|
||||||
@ -65,7 +64,10 @@ namespace Artemis.UI.Routing
|
|||||||
new RouteRegistration<AccountTabViewModel>("account"),
|
new RouteRegistration<AccountTabViewModel>("account"),
|
||||||
new RouteRegistration<AboutTabViewModel>("about")
|
new RouteRegistration<AboutTabViewModel>("about")
|
||||||
]),
|
]),
|
||||||
new RouteRegistration<ProfileEditorViewModel>("profile-editor/{profileConfigurationId:guid}")
|
new RouteRegistration<ProfileViewModel>("profile/{profileConfigurationId:guid}", [
|
||||||
|
new RouteRegistration<ProfileEditorViewModel>("editor"),
|
||||||
|
new RouteRegistration<WorkshopProfileViewModel>("workshop")
|
||||||
|
]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -21,11 +21,14 @@ public partial class DebugView : ReactiveAppWindow<DebugViewModel>
|
|||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
Observable.FromEventPattern(x => ViewModel!.ActivationRequested += x, x => ViewModel!.ActivationRequested -= x).Subscribe(_ =>
|
DebugViewModel vm = ViewModel!;
|
||||||
|
Observable.FromEventPattern(x => vm.ActivationRequested += x, x => vm.ActivationRequested -= x)
|
||||||
|
.Subscribe(_ =>
|
||||||
{
|
{
|
||||||
WindowState = WindowState.Normal;
|
WindowState = WindowState.Normal;
|
||||||
Activate();
|
Activate();
|
||||||
}).DisposeWith(d);
|
})
|
||||||
|
.DisposeWith(d);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:debugger="clr-namespace:Artemis.UI.Screens.Debugger.Performance;assembly=Artemis.UI"
|
xmlns:debugger="clr-namespace:Artemis.UI.Screens.Debugger.Performance;assembly=Artemis.UI"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:DataType="debugger:PerformanceDebugViewModel"
|
x:DataType="debugger:PerformanceDebugViewModel"
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Device"
|
xmlns:local="clr-namespace:Artemis.UI.Screens.Device"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||||
xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
|
|
||||||
xmlns:general="clr-namespace:Artemis.UI.Screens.Device.General"
|
xmlns:general="clr-namespace:Artemis.UI.Screens.Device.General"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650"
|
||||||
x:Class="Artemis.UI.Screens.Device.General.DeviceGeneralTabView"
|
x:Class="Artemis.UI.Screens.Device.General.DeviceGeneralTabView"
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
|
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:inputMappings="clr-namespace:Artemis.UI.Screens.Device.InputMappings"
|
xmlns:inputMappings="clr-namespace:Artemis.UI.Screens.Device.InputMappings"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:services="clr-namespace:Artemis.WebClient.Workshop.Services;assembly=Artemis.WebClient.Workshop"
|
|
||||||
xmlns:layoutProviders="clr-namespace:Artemis.UI.Screens.Device.Layout.LayoutProviders"
|
xmlns:layoutProviders="clr-namespace:Artemis.UI.Screens.Device.Layout.LayoutProviders"
|
||||||
xmlns:models="clr-namespace:Artemis.WebClient.Workshop.Models;assembly=Artemis.WebClient.Workshop"
|
xmlns:models="clr-namespace:Artemis.WebClient.Workshop.Models;assembly=Artemis.WebClient.Workshop"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
|
|
||||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||||
xmlns:shared="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
xmlns:shared="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||||
xmlns:leds="clr-namespace:Artemis.UI.Screens.Device.Leds"
|
xmlns:leds="clr-namespace:Artemis.UI.Screens.Device.Leds"
|
||||||
|
|||||||
@ -7,7 +7,8 @@
|
|||||||
xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites"
|
xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisitesInstallDialogView"
|
x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisitesInstallDialogView"
|
||||||
x:DataType="plugins:PluginPrerequisitesInstallDialogViewModel">
|
x:DataType="plugins:PluginPrerequisitesInstallDialogViewModel"
|
||||||
|
Width="800">
|
||||||
<UserControl.Styles>
|
<UserControl.Styles>
|
||||||
<Styles>
|
<Styles>
|
||||||
<Style Selector="Border.status-border">
|
<Style Selector="Border.status-border">
|
||||||
@ -24,7 +25,7 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</Styles>
|
</Styles>
|
||||||
</UserControl.Styles>
|
</UserControl.Styles>
|
||||||
<Grid ColumnDefinitions="350,*" Width="800">
|
<Grid ColumnDefinitions="350,*">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition MinHeight="200" />
|
<RowDefinition MinHeight="200" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|||||||
@ -7,7 +7,8 @@
|
|||||||
xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites"
|
xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisitesUninstallDialogView"
|
x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisitesUninstallDialogView"
|
||||||
x:DataType="plugins:PluginPrerequisitesUninstallDialogViewModel">
|
x:DataType="plugins:PluginPrerequisitesUninstallDialogViewModel"
|
||||||
|
Width="800">
|
||||||
<UserControl.Styles>
|
<UserControl.Styles>
|
||||||
<Styles>
|
<Styles>
|
||||||
<Style Selector="Border.status-border">
|
<Style Selector="Border.status-border">
|
||||||
@ -24,7 +25,7 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</Styles>
|
</Styles>
|
||||||
</UserControl.Styles>
|
</UserControl.Styles>
|
||||||
<Grid ColumnDefinitions="350,*" Width="800">
|
<Grid ColumnDefinitions="350,*">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition MinHeight="200" />
|
<RowDefinition MinHeight="200" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|||||||
@ -3,8 +3,6 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
|
||||||
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
|
|
||||||
xmlns:features="clr-namespace:Artemis.UI.Screens.Plugins.Features"
|
xmlns:features="clr-namespace:Artemis.UI.Screens.Plugins.Features"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Plugins.Features.PluginFeatureView"
|
x:Class="Artemis.UI.Screens.Plugins.Features.PluginFeatureView"
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia"
|
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia"
|
||||||
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
|
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
|||||||
@ -19,14 +19,11 @@ public partial class PluginSettingsWindowView : ReactiveAppWindow<PluginSettings
|
|||||||
|
|
||||||
this.WhenActivated(disposables =>
|
this.WhenActivated(disposables =>
|
||||||
{
|
{
|
||||||
Observable.FromEventPattern(
|
PluginSettingsWindowViewModel vm = ViewModel!;
|
||||||
x => ViewModel!.ConfigurationViewModel.CloseRequested += x,
|
Observable.FromEventPattern(x => vm.ConfigurationViewModel.CloseRequested += x, x => vm.ConfigurationViewModel.CloseRequested -= x)
|
||||||
x => ViewModel!.ConfigurationViewModel.CloseRequested -= x
|
|
||||||
)
|
|
||||||
.Subscribe(_ => Close())
|
.Subscribe(_ => Close())
|
||||||
.DisposeWith(disposables);
|
.DisposeWith(disposables);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -4,7 +4,6 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
|
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
|
||||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Plugins.PluginView"
|
x:Class="Artemis.UI.Screens.Plugins.PluginView"
|
||||||
|
|||||||
@ -6,11 +6,11 @@ using System.Reactive;
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
|
||||||
using Artemis.UI.Exceptions;
|
using Artemis.UI.Exceptions;
|
||||||
|
using Artemis.UI.Services;
|
||||||
|
using Artemis.UI.Services.Interfaces;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Artemis.UI.Shared.Services.Builders;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Material.Icons;
|
using Material.Icons;
|
||||||
@ -21,9 +21,7 @@ namespace Artemis.UI.Screens.Plugins;
|
|||||||
|
|
||||||
public partial class PluginViewModel : ActivatableViewModelBase
|
public partial class PluginViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
private readonly ICoreService _coreService;
|
private readonly IPluginInteractionService _pluginInteractionService;
|
||||||
private readonly INotificationService _notificationService;
|
|
||||||
private readonly IPluginManagementService _pluginManagementService;
|
|
||||||
private readonly IWindowService _windowService;
|
private readonly IWindowService _windowService;
|
||||||
private Window? _settingsWindow;
|
private Window? _settingsWindow;
|
||||||
[Notify] private bool _canInstallPrerequisites;
|
[Notify] private bool _canInstallPrerequisites;
|
||||||
@ -31,18 +29,11 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
|||||||
[Notify] private bool _enabling;
|
[Notify] private bool _enabling;
|
||||||
[Notify] private Plugin _plugin;
|
[Notify] private Plugin _plugin;
|
||||||
|
|
||||||
public PluginViewModel(Plugin plugin,
|
public PluginViewModel(Plugin plugin, ReactiveCommand<Unit, Unit>? reload, IWindowService windowService, IPluginInteractionService pluginInteractionService)
|
||||||
ReactiveCommand<Unit, Unit>? reload,
|
|
||||||
ICoreService coreService,
|
|
||||||
IWindowService windowService,
|
|
||||||
INotificationService notificationService,
|
|
||||||
IPluginManagementService pluginManagementService)
|
|
||||||
{
|
{
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
_coreService = coreService;
|
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
_notificationService = notificationService;
|
_pluginInteractionService = pluginInteractionService;
|
||||||
_pluginManagementService = pluginManagementService;
|
|
||||||
|
|
||||||
Platforms = new ObservableCollection<PluginPlatformViewModel>();
|
Platforms = new ObservableCollection<PluginPlatformViewModel>();
|
||||||
if (Plugin.Info.Platforms != null)
|
if (Plugin.Info.Platforms != null)
|
||||||
@ -88,7 +79,6 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
|||||||
public ReactiveCommand<Unit, Unit> OpenPluginDirectory { get; }
|
public ReactiveCommand<Unit, Unit> OpenPluginDirectory { get; }
|
||||||
|
|
||||||
public ObservableCollection<PluginPlatformViewModel> Platforms { get; }
|
public ObservableCollection<PluginPlatformViewModel> Platforms { get; }
|
||||||
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
|
|
||||||
public bool IsEnabled => Plugin.IsEnabled;
|
public bool IsEnabled => Plugin.IsEnabled;
|
||||||
|
|
||||||
public async Task UpdateEnabled(bool enable)
|
public async Task UpdateEnabled(bool enable)
|
||||||
@ -97,55 +87,15 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (!enable)
|
if (!enable)
|
||||||
{
|
await _pluginInteractionService.DisablePlugin(Plugin);
|
||||||
try
|
else
|
||||||
{
|
|
||||||
await Task.Run(() => _pluginManagementService.DisablePlugin(Plugin, true));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
await ShowUpdateEnableFailure(enable, e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
Enabling = true;
|
Enabling = true;
|
||||||
if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
|
await _pluginInteractionService.EnablePlugin(Plugin, false);
|
||||||
{
|
|
||||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?");
|
|
||||||
if (!confirmed)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if all prerequisites are met async
|
|
||||||
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
|
|
||||||
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled || f.EnabledInStorage));
|
|
||||||
|
|
||||||
if (subjects.Any(s => !s.ArePrerequisitesMet()))
|
|
||||||
{
|
|
||||||
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
|
|
||||||
if (!subjects.All(s => s.ArePrerequisitesMet()))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Run(() => _pluginManagementService.EnablePlugin(Plugin, true, true));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
await ShowUpdateEnableFailure(enable, e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Enabling = false;
|
Enabling = false;
|
||||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CheckPrerequisites()
|
public void CheckPrerequisites()
|
||||||
@ -220,43 +170,12 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
|||||||
|
|
||||||
private async Task ExecuteRemoveSettings()
|
private async Task ExecuteRemoveSettings()
|
||||||
{
|
{
|
||||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?");
|
await _pluginInteractionService.RemovePluginSettings(Plugin);
|
||||||
if (!confirmed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
bool wasEnabled = IsEnabled;
|
|
||||||
|
|
||||||
if (IsEnabled)
|
|
||||||
await UpdateEnabled(false);
|
|
||||||
|
|
||||||
_pluginManagementService.RemovePluginSettings(Plugin);
|
|
||||||
|
|
||||||
if (wasEnabled)
|
|
||||||
await UpdateEnabled(true);
|
|
||||||
|
|
||||||
_notificationService.CreateNotification().WithTitle("Cleared plugin settings.").Show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteRemove()
|
private async Task ExecuteRemove()
|
||||||
{
|
{
|
||||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Remove plugin", "Are you sure you want to remove this plugin?");
|
await _pluginInteractionService.RemovePlugin(Plugin);
|
||||||
if (!confirmed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If the plugin or any of its features has uninstall actions, offer to run these
|
|
||||||
await ExecuteRemovePrerequisites(true);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_pluginManagementService.RemovePlugin(Plugin, false);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_windowService.ShowExceptionDialog("Failed to remove plugin", e);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
_notificationService.CreateNotification().WithTitle("Removed plugin.").Show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteShowLogsFolder()
|
private void ExecuteShowLogsFolder()
|
||||||
@ -271,20 +190,6 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ShowUpdateEnableFailure(bool enable, Exception e)
|
|
||||||
{
|
|
||||||
string action = enable ? "enable" : "disable";
|
|
||||||
ContentDialogBuilder builder = _windowService.CreateContentDialog()
|
|
||||||
.WithTitle($"Failed to {action} plugin {Plugin.Info.Name}")
|
|
||||||
.WithContent(e.Message)
|
|
||||||
.HavingPrimaryButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder));
|
|
||||||
// If available, add a secondary button pointing to the support page
|
|
||||||
if (Plugin.Info.HelpPage != null)
|
|
||||||
builder = builder.HavingSecondaryButton(b => b.WithText("Open support page").WithAction(() => Utilities.OpenUrl(Plugin.Info.HelpPage.ToString())));
|
|
||||||
|
|
||||||
await builder.ShowAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPluginToggled(object? sender, EventArgs e)
|
private void OnPluginToggled(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
|
|
||||||
xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites"
|
xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Plugins.Prerequisites.PluginPrerequisiteActionView"
|
x:Class="Artemis.UI.Screens.Plugins.Prerequisites.PluginPrerequisiteActionView"
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
|
|
||||||
xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites"
|
xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Plugins.Prerequisites.PluginPrerequisiteView"
|
x:Class="Artemis.UI.Screens.Plugins.Prerequisites.PluginPrerequisiteView"
|
||||||
|
|||||||
@ -79,11 +79,6 @@ public partial class ProfileTreeViewModel : TreeItemViewModel
|
|||||||
|
|
||||||
public override bool SupportsChildren => true;
|
public override bool SupportsChildren => true;
|
||||||
|
|
||||||
public void UpdateCanPaste()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Task ExecuteDuplicate()
|
protected override Task ExecuteDuplicate()
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
|
|||||||
@ -89,7 +89,7 @@
|
|||||||
Background="{DynamicResource ControlFillColorDefaultBrush}"
|
Background="{DynamicResource ControlFillColorDefaultBrush}"
|
||||||
IsVisible="{CompiledBinding ProfileConfiguration, Converter={x:Static ObjectConverters.IsNotNull}}">
|
IsVisible="{CompiledBinding ProfileConfiguration, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||||
<StackPanel Orientation="Horizontal" Margin="8">
|
<StackPanel Orientation="Horizontal" Margin="8">
|
||||||
<shared:ProfileConfigurationIcon ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}" Width="18" Height="18" Margin="0 0 5 0" />
|
<shared:ProfileConfigurationIcon ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}" Width="18" Height="18" CornerRadius="3" Margin="0 0 5 0" />
|
||||||
<TextBlock Text="{CompiledBinding ProfileConfiguration.Name}" />
|
<TextBlock Text="{CompiledBinding ProfileConfiguration.Name}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|||||||
@ -27,8 +27,9 @@ public partial class VisualEditorView : ReactiveUserControl<VisualEditorViewMode
|
|||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
ViewModel!.AutoFitRequested += ViewModelOnAutoFitRequested;
|
VisualEditorViewModel vm = ViewModel!;
|
||||||
Disposable.Create(() => ViewModel.AutoFitRequested -= ViewModelOnAutoFitRequested).DisposeWith(d);
|
vm!.AutoFitRequested += ViewModelOnAutoFitRequested;
|
||||||
|
Disposable.Create(() => vm.AutoFitRequested -= ViewModelOnAutoFitRequested).DisposeWith(d);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.WhenAnyValue(v => v.Bounds).Where(_ => !_movedByUser).Subscribe(_ => AutoFit(true));
|
this.WhenAnyValue(v => v.Bounds).Where(_ => !_movedByUser).Subscribe(_ => AutoFit(true));
|
||||||
|
|||||||
@ -15,11 +15,8 @@ using Artemis.UI.Screens.ProfileEditor.StatusBar;
|
|||||||
using Artemis.UI.Screens.ProfileEditor.VisualEditor;
|
using Artemis.UI.Screens.ProfileEditor.VisualEditor;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
using Artemis.UI.Shared.Services;
|
|
||||||
using Artemis.UI.Shared.Services.MainWindow;
|
using Artemis.UI.Shared.Services.MainWindow;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
using Artemis.WebClient.Workshop.Models;
|
|
||||||
using Artemis.WebClient.Workshop.Services;
|
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
@ -27,14 +24,12 @@ using ReactiveUI;
|
|||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor;
|
namespace Artemis.UI.Screens.ProfileEditor;
|
||||||
|
|
||||||
public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewModelParameters>, IMainScreenViewModel
|
public partial class ProfileEditorViewModel : RoutableScreen<ProfileViewModelParameters>, IMainScreenViewModel
|
||||||
{
|
{
|
||||||
private readonly IProfileEditorService _profileEditorService;
|
private readonly IProfileEditorService _profileEditorService;
|
||||||
private readonly IProfileService _profileService;
|
private readonly IProfileService _profileService;
|
||||||
private readonly ISettingsService _settingsService;
|
private readonly ISettingsService _settingsService;
|
||||||
private readonly IMainWindowService _mainWindowService;
|
private readonly IMainWindowService _mainWindowService;
|
||||||
private readonly IWorkshopService _workshopService;
|
|
||||||
private readonly IWindowService _windowService;
|
|
||||||
private readonly SourceList<IToolViewModel> _tools;
|
private readonly SourceList<IToolViewModel> _tools;
|
||||||
private ObservableAsPropertyHelper<ProfileEditorHistory?>? _history;
|
private ObservableAsPropertyHelper<ProfileEditorHistory?>? _history;
|
||||||
private ObservableAsPropertyHelper<bool>? _suspendedEditing;
|
private ObservableAsPropertyHelper<bool>? _suspendedEditing;
|
||||||
@ -53,16 +48,12 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
|
|||||||
StatusBarViewModel statusBarViewModel,
|
StatusBarViewModel statusBarViewModel,
|
||||||
IEnumerable<IToolViewModel> toolViewModels,
|
IEnumerable<IToolViewModel> toolViewModels,
|
||||||
IMainWindowService mainWindowService,
|
IMainWindowService mainWindowService,
|
||||||
IInputService inputService,
|
IInputService inputService)
|
||||||
IWorkshopService workshopService,
|
|
||||||
IWindowService windowService)
|
|
||||||
{
|
{
|
||||||
_profileService = profileService;
|
_profileService = profileService;
|
||||||
_profileEditorService = profileEditorService;
|
_profileEditorService = profileEditorService;
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
_mainWindowService = mainWindowService;
|
_mainWindowService = mainWindowService;
|
||||||
_workshopService = workshopService;
|
|
||||||
_windowService = windowService;
|
|
||||||
|
|
||||||
_tools = new SourceList<IToolViewModel>();
|
_tools = new SourceList<IToolViewModel>();
|
||||||
_tools.AddRange(toolViewModels);
|
_tools.AddRange(toolViewModels);
|
||||||
@ -75,6 +66,7 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
|
|||||||
Tools = tools;
|
Tools = tools;
|
||||||
visualEditorViewModel.SetTools(_tools);
|
visualEditorViewModel.SetTools(_tools);
|
||||||
|
|
||||||
|
ParameterSource = ParameterSource.Route;
|
||||||
StatusBarViewModel = statusBarViewModel;
|
StatusBarViewModel = statusBarViewModel;
|
||||||
VisualEditorViewModel = visualEditorViewModel;
|
VisualEditorViewModel = visualEditorViewModel;
|
||||||
ProfileTreeViewModel = profileTreeViewModel;
|
ProfileTreeViewModel = profileTreeViewModel;
|
||||||
@ -193,7 +185,7 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
|
|||||||
#region Overrides of RoutableScreen<object,ProfileEditorViewModelParameters>
|
#region Overrides of RoutableScreen<object,ProfileEditorViewModelParameters>
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task OnNavigating(ProfileEditorViewModelParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
|
public override async Task OnNavigating(ProfileViewModelParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ProfileConfiguration? profileConfiguration = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == parameters.ProfileId);
|
ProfileConfiguration? profileConfiguration = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == parameters.ProfileId);
|
||||||
|
|
||||||
@ -204,23 +196,6 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the profile is from the workshop, warn the user that auto-updates will be disabled
|
|
||||||
InstalledEntry? workshopEntry = _workshopService.GetInstalledEntryByProfile(profileConfiguration);
|
|
||||||
if (workshopEntry != null && workshopEntry.AutoUpdate)
|
|
||||||
{
|
|
||||||
bool confirmed = await _windowService.ShowConfirmContentDialog(
|
|
||||||
"Editing a workshop profile",
|
|
||||||
"You are about to edit a profile from the workshop, to preserve your changes auto-updating will be disabled.",
|
|
||||||
"Disable auto-update");
|
|
||||||
if (confirmed)
|
|
||||||
_workshopService.SetAutoUpdate(workshopEntry, false);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
args.Cancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await _profileEditorService.ChangeCurrentProfileConfiguration(profileConfiguration);
|
await _profileEditorService.ChangeCurrentProfileConfiguration(profileConfiguration);
|
||||||
ProfileConfiguration = profileConfiguration;
|
ProfileConfiguration = profileConfiguration;
|
||||||
}
|
}
|
||||||
@ -237,8 +212,3 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ProfileEditorViewModelParameters
|
|
||||||
{
|
|
||||||
public Guid ProfileId { get; set; }
|
|
||||||
}
|
|
||||||
14
src/Artemis.UI/Screens/ProfileEditor/ProfileView.axaml
Normal file
14
src/Artemis.UI/Screens/ProfileEditor/ProfileView.axaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
xmlns:ui="clr-namespace:Artemis.UI"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileView">
|
||||||
|
<controls:Frame Name="RouterFrame" IsNavigationStackEnabled="False" CacheSize="0">
|
||||||
|
<controls:Frame.NavigationPageFactory>
|
||||||
|
<ui:PageFactory/>
|
||||||
|
</controls:Frame.NavigationPageFactory>
|
||||||
|
</controls:Frame>
|
||||||
|
</UserControl>
|
||||||
19
src/Artemis.UI/Screens/ProfileEditor/ProfileView.axaml.cs
Normal file
19
src/Artemis.UI/Screens/ProfileEditor/ProfileView.axaml.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using Artemis.UI.Shared.Routing;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using ReactiveUI;
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor;
|
||||||
|
|
||||||
|
public partial class ProfileView : ReactiveUserControl<ProfileViewModel>
|
||||||
|
{
|
||||||
|
public ProfileView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.Screen)
|
||||||
|
.WhereNotNull()
|
||||||
|
.Subscribe(screen => RouterFrame.NavigateFromObject(screen))
|
||||||
|
.DisposeWith(d));
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/Artemis.UI/Screens/ProfileEditor/ProfileViewModel.cs
Normal file
62
src/Artemis.UI/Screens/ProfileEditor/ProfileViewModel.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Routing;
|
||||||
|
using Artemis.WebClient.Workshop.Models;
|
||||||
|
using Artemis.WebClient.Workshop.Services;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor;
|
||||||
|
|
||||||
|
public class ProfileViewModel : RoutableHostScreen<RoutableScreen, ProfileViewModelParameters>, IMainScreenViewModel
|
||||||
|
{
|
||||||
|
private readonly IProfileService _profileService;
|
||||||
|
private readonly IWorkshopService _workshopService;
|
||||||
|
private readonly ObservableAsPropertyHelper<ViewModelBase?> _titleBarViewModel;
|
||||||
|
|
||||||
|
public ProfileViewModel(IProfileService profileService, IWorkshopService workshopService)
|
||||||
|
{
|
||||||
|
_profileService = profileService;
|
||||||
|
_workshopService = workshopService;
|
||||||
|
|
||||||
|
_titleBarViewModel = this.WhenAnyValue(vm => vm.Screen).Select(screen => screen as IMainScreenViewModel)
|
||||||
|
.Select(mainScreen => mainScreen?.TitleBarViewModel)
|
||||||
|
.ToProperty(this, vm => vm.TitleBarViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewModelBase? TitleBarViewModel => _titleBarViewModel.Value;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override async Task OnNavigating(ProfileViewModelParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ProfileConfiguration? profileConfiguration = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == parameters.ProfileId);
|
||||||
|
|
||||||
|
// If the profile doesn't exist, cancel navigation
|
||||||
|
if (profileConfiguration == null)
|
||||||
|
{
|
||||||
|
args.Cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the profile is from the workshop, redirect to the workshop page
|
||||||
|
InstalledEntry? workshopEntry = _workshopService.GetInstalledEntryByProfile(profileConfiguration);
|
||||||
|
if (workshopEntry != null && workshopEntry.AutoUpdate)
|
||||||
|
{
|
||||||
|
if (!args.Path.EndsWith("workshop"))
|
||||||
|
await args.Router.Navigate($"profile/{parameters.ProfileId}/workshop");
|
||||||
|
}
|
||||||
|
// Otherwise, show the profile editor if not already on the editor page
|
||||||
|
else if (!args.Path.EndsWith("editor"))
|
||||||
|
await args.Router.Navigate($"profile/{parameters.ProfileId}/editor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProfileViewModelParameters
|
||||||
|
{
|
||||||
|
public Guid ProfileId { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:profileEditor="clr-namespace:Artemis.UI.Screens.ProfileEditor"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.ProfileEditor.WorkshopProfileView"
|
||||||
|
x:DataType="profileEditor:WorkshopProfileViewModel">
|
||||||
|
<Border Classes="router-container">
|
||||||
|
|
||||||
|
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Vertical" Spacing="10" MaxWidth="800">
|
||||||
|
<ContentControl Content="{CompiledBinding EntryViewModel}"></ContentControl>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
The profile you're opening is a workshop profile.
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
You cannot make change to it without disabling auto-update, as any updates to the profile on the workshop would override your own modifications.
|
||||||
|
</TextBlock>
|
||||||
|
<Button Command="{CompiledBinding DisableAutoUpdate}" Classes="accent">Disable auto-update</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor;
|
||||||
|
|
||||||
|
public partial class WorkshopProfileView : ReactiveUserControl<WorkshopProfileViewModel>
|
||||||
|
{
|
||||||
|
public WorkshopProfileView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Screens.Workshop.Library.Tabs;
|
||||||
|
using Artemis.UI.Shared.Routing;
|
||||||
|
using Artemis.WebClient.Workshop.Models;
|
||||||
|
using Artemis.WebClient.Workshop.Services;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor;
|
||||||
|
|
||||||
|
public partial class WorkshopProfileViewModel : RoutableScreen<ProfileViewModelParameters>
|
||||||
|
{
|
||||||
|
private readonly IProfileService _profileService;
|
||||||
|
private readonly IWorkshopService _workshopService;
|
||||||
|
private readonly IRouter _router;
|
||||||
|
private readonly Func<InstalledEntry, InstalledTabItemViewModel> _getInstalledTabItemViewModel;
|
||||||
|
|
||||||
|
[Notify] private ProfileConfiguration? _profileConfiguration;
|
||||||
|
[Notify] private InstalledEntry? _workshopEntry;
|
||||||
|
[Notify] private InstalledTabItemViewModel? _entryViewModel;
|
||||||
|
|
||||||
|
public WorkshopProfileViewModel(IProfileService profileService, IWorkshopService workshopService, IRouter router, Func<InstalledEntry, InstalledTabItemViewModel> getInstalledTabItemViewModel)
|
||||||
|
{
|
||||||
|
_profileService = profileService;
|
||||||
|
_workshopService = workshopService;
|
||||||
|
_router = router;
|
||||||
|
_getInstalledTabItemViewModel = getInstalledTabItemViewModel;
|
||||||
|
ParameterSource = ParameterSource.Route;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DisableAutoUpdate()
|
||||||
|
{
|
||||||
|
if (WorkshopEntry != null)
|
||||||
|
{
|
||||||
|
_workshopService.SetAutoUpdate(WorkshopEntry, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ProfileConfiguration != null)
|
||||||
|
{
|
||||||
|
await _router.Navigate($"profile/{ProfileConfiguration.ProfileId}/editor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task OnNavigating(ProfileViewModelParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ProfileConfiguration = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == parameters.ProfileId);
|
||||||
|
|
||||||
|
// If the profile doesn't exist, cancel navigation
|
||||||
|
if (ProfileConfiguration == null)
|
||||||
|
{
|
||||||
|
args.Cancel();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkshopEntry = _workshopService.GetInstalledEntryByProfile(ProfileConfiguration);
|
||||||
|
EntryViewModel = WorkshopEntry != null ? _getInstalledTabItemViewModel(WorkshopEntry) : null;
|
||||||
|
if (EntryViewModel != null)
|
||||||
|
EntryViewModel.DisplayManagement = false;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -83,7 +83,7 @@ public class RootViewModel : RoutableHostScreen<RoutableScreen>, IMainWindowProv
|
|||||||
_coreService.Initialized += (_, _) => Dispatcher.UIThread.InvokeAsync(OpenMainWindow);
|
_coreService.Initialized += (_, _) => Dispatcher.UIThread.InvokeAsync(OpenMainWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.Run(() =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -93,7 +93,7 @@ public class RootViewModel : RoutableHostScreen<RoutableScreen>, IMainWindowProv
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Workshop service goes first so it has a chance to clean up old workshop entries and introduce new ones
|
// Workshop service goes first so it has a chance to clean up old workshop entries and introduce new ones
|
||||||
workshopService.Initialize();
|
await workshopService.Initialize();
|
||||||
// Core is initialized now that everything is ready to go
|
// Core is initialized now that everything is ready to go
|
||||||
coreService.Initialize();
|
coreService.Initialize();
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,8 @@ public partial class SplashView : ReactiveWindow<SplashViewModel>
|
|||||||
#endif
|
#endif
|
||||||
this.WhenActivated(disposables =>
|
this.WhenActivated(disposables =>
|
||||||
{
|
{
|
||||||
Observable.FromEventPattern(x => ViewModel!.CoreService.Initialized += x, x => ViewModel!.CoreService.Initialized -= x)
|
SplashViewModel vm = ViewModel!;
|
||||||
|
Observable.FromEventPattern(x => vm.CoreService.Initialized += x, x => vm.CoreService.Initialized -= x)
|
||||||
.Subscribe(_ => Dispatcher.UIThread.Post(Close))
|
.Subscribe(_ => Dispatcher.UIThread.Post(Close))
|
||||||
.DisposeWith(disposables);
|
.DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Services;
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.WebClient.Workshop.Services;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Root;
|
namespace Artemis.UI.Screens.Root;
|
||||||
@ -10,12 +11,12 @@ public partial class SplashViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
[Notify] private string _status;
|
[Notify] private string _status;
|
||||||
|
|
||||||
public SplashViewModel(ICoreService coreService, IPluginManagementService pluginManagementService)
|
public SplashViewModel(ICoreService coreService, IPluginManagementService pluginManagementService, IWorkshopService workshopService)
|
||||||
{
|
{
|
||||||
CoreService = coreService;
|
CoreService = coreService;
|
||||||
_status = "Initializing Core";
|
_status = "Initializing Core";
|
||||||
|
|
||||||
pluginManagementService.CopyingBuildInPlugins += OnPluginManagementServiceOnCopyingBuildInPluginsManagement;
|
workshopService.MigratingBuildInPlugins += WorkshopServiceOnMigratingBuildInPlugins;
|
||||||
pluginManagementService.PluginLoading += OnPluginManagementServiceOnPluginManagementLoading;
|
pluginManagementService.PluginLoading += OnPluginManagementServiceOnPluginManagementLoading;
|
||||||
pluginManagementService.PluginLoaded += OnPluginManagementServiceOnPluginManagementLoaded;
|
pluginManagementService.PluginLoaded += OnPluginManagementServiceOnPluginManagementLoaded;
|
||||||
pluginManagementService.PluginEnabling += PluginManagementServiceOnPluginManagementEnabling;
|
pluginManagementService.PluginEnabling += PluginManagementServiceOnPluginManagementEnabling;
|
||||||
@ -26,6 +27,11 @@ public partial class SplashViewModel : ViewModelBase
|
|||||||
|
|
||||||
public ICoreService CoreService { get; }
|
public ICoreService CoreService { get; }
|
||||||
|
|
||||||
|
private void WorkshopServiceOnMigratingBuildInPlugins(object? sender, EventArgs args)
|
||||||
|
{
|
||||||
|
Status = "Migrating built-in plugins";
|
||||||
|
}
|
||||||
|
|
||||||
private void OnPluginManagementServiceOnPluginManagementLoaded(object? sender, PluginEventArgs args)
|
private void OnPluginManagementServiceOnPluginManagementLoaded(object? sender, PluginEventArgs args)
|
||||||
{
|
{
|
||||||
Status = "Initializing UI";
|
Status = "Initializing UI";
|
||||||
@ -55,9 +61,4 @@ public partial class SplashViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
Status = "Initializing UI";
|
Status = "Initializing UI";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPluginManagementServiceOnCopyingBuildInPluginsManagement(object? sender, EventArgs args)
|
|
||||||
{
|
|
||||||
Status = "Updating built-in plugins";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -2,7 +2,6 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:vm="clr-namespace:Artemis.UI.Screens.Settings;assembly=Artemis.UI"
|
xmlns:vm="clr-namespace:Artemis.UI.Screens.Settings;assembly=Artemis.UI"
|
||||||
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:settings="clr-namespace:Artemis.UI.Screens.Settings"
|
xmlns:settings="clr-namespace:Artemis.UI.Screens.Settings"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
|
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
|
||||||
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Settings.PluginsTabView"
|
x:Class="Artemis.UI.Screens.Settings.PluginsTabView"
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:settings="clr-namespace:Artemis.UI.Screens.Settings"
|
xmlns:settings="clr-namespace:Artemis.UI.Screens.Settings"
|
||||||
xmlns:updating="clr-namespace:Artemis.UI.Screens.Settings.Updating"
|
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:ui="clr-namespace:Artemis.UI"
|
xmlns:ui="clr-namespace:Artemis.UI"
|
||||||
|
|||||||
@ -187,7 +187,7 @@ public partial class ProfileConfigurationEditViewModel : DialogViewModelBase<Pro
|
|||||||
if (result == null)
|
if (result == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SelectedBitmapSource = BitmapExtensions.LoadAndResize(result[0], 128);
|
SelectedBitmapSource = BitmapExtensions.LoadAndResize(result[0], 128, false);
|
||||||
_selectedIconPath = result[0];
|
_selectedIconPath = result[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -65,7 +65,7 @@ public partial class SidebarCategoryViewModel : ActivatableViewModelBase
|
|||||||
// Navigate on selection change
|
// Navigate on selection change
|
||||||
this.WhenAnyValue(vm => vm.SelectedProfileConfiguration)
|
this.WhenAnyValue(vm => vm.SelectedProfileConfiguration)
|
||||||
.WhereNotNull()
|
.WhereNotNull()
|
||||||
.Subscribe(s => _router.Navigate($"profile-editor/{s.ProfileConfiguration.ProfileId}", new RouterNavigationOptions {IgnoreOnPartialMatch = true, RecycleScreens = false}))
|
.Subscribe(s => _router.Navigate($"profile/{s.ProfileConfiguration.ProfileId}/editor", new RouterNavigationOptions {IgnoreOnPartialMatch = true, RecycleScreens = false}))
|
||||||
.DisposeWith(d);
|
.DisposeWith(d);
|
||||||
|
|
||||||
_router.CurrentPath.WhereNotNull().Subscribe(r => SelectedProfileConfiguration = ProfileConfigurations.FirstOrDefault(c => c.Matches(r))).DisposeWith(d);
|
_router.CurrentPath.WhereNotNull().Subscribe(r => SelectedProfileConfiguration = ProfileConfigurations.FirstOrDefault(c => c.Matches(r))).DisposeWith(d);
|
||||||
|
|||||||
@ -78,6 +78,7 @@
|
|||||||
ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}"
|
ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}"
|
||||||
Width="22"
|
Width="22"
|
||||||
Height="22"
|
Height="22"
|
||||||
|
CornerRadius="4"
|
||||||
Margin="0 0 5 0">
|
Margin="0 0 5 0">
|
||||||
<shared:ProfileConfigurationIcon.Transitions>
|
<shared:ProfileConfigurationIcon.Transitions>
|
||||||
<Transitions>
|
<Transitions>
|
||||||
|
|||||||
@ -132,6 +132,6 @@ public class SidebarProfileConfigurationViewModel : ActivatableViewModelBase
|
|||||||
|
|
||||||
public bool Matches(string s)
|
public bool Matches(string s)
|
||||||
{
|
{
|
||||||
return s.StartsWith("profile-editor") && s.EndsWith(ProfileConfiguration.ProfileId.ToString());
|
return s.StartsWith($"profile/{ProfileConfiguration.ProfileId}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3,7 +3,6 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:sidebar="clr-namespace:Artemis.UI.Screens.Sidebar"
|
xmlns:sidebar="clr-namespace:Artemis.UI.Screens.Sidebar"
|
||||||
mc:Ignorable="d" d:DesignWidth="240" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="240" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Sidebar.SidebarView"
|
x:Class="Artemis.UI.Screens.Sidebar.SidebarView"
|
||||||
|
|||||||
@ -11,16 +11,16 @@
|
|||||||
x:DataType="startupWizard:StartupWizardViewModel"
|
x:DataType="startupWizard:StartupWizardViewModel"
|
||||||
Icon="/Assets/Images/Logo/application.ico"
|
Icon="/Assets/Images/Logo/application.ico"
|
||||||
Title="Artemis | Startup wizard"
|
Title="Artemis | Startup wizard"
|
||||||
Width="1000"
|
Width="1050"
|
||||||
Height="735"
|
Height="735"
|
||||||
WindowStartupLocation="CenterOwner">
|
WindowStartupLocation="CenterOwner">
|
||||||
<Grid Margin="15" RowDefinitions="Auto,*,Auto" ColumnDefinitions="Auto,*">
|
<Grid Margin="15" RowDefinitions="Auto,*,Auto">
|
||||||
<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" RowDefinitions="*,*" ColumnDefinitions="Auto,*,Auto">
|
<!-- Header -->
|
||||||
|
<Grid Grid.Row="0" RowDefinitions="*,*" ColumnDefinitions="Auto,*,Auto">
|
||||||
<Image Grid.Column="0" Grid.RowSpan="2" Width="65" Height="65" VerticalAlignment="Center" Source="/Assets/Images/Logo/bow.png" Margin="0 0 20 0" />
|
<Image Grid.Column="0" Grid.RowSpan="2" Width="65" Height="65" VerticalAlignment="Center" Source="/Assets/Images/Logo/bow.png" Margin="0 0 20 0" />
|
||||||
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom">
|
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom">
|
||||||
Artemis 2
|
Artemis 2
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
|
||||||
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
|
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
|
||||||
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=wizard">
|
<HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com?mtm_campaign=artemis&mtm_kwd=wizard">
|
||||||
<avalonia:MaterialIcon Kind="Web" />
|
<avalonia:MaterialIcon Kind="Web" />
|
||||||
@ -32,13 +32,11 @@
|
|||||||
<avalonia:MaterialIcon Kind="BookOpenOutline" />
|
<avalonia:MaterialIcon Kind="BookOpenOutline" />
|
||||||
</HyperlinkButton>
|
</HyperlinkButton>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock Grid.Row="1"
|
<TextBlock Grid.Row="1"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
Classes="subtitle"
|
Classes="subtitle"
|
||||||
Text="{CompiledBinding Version}" />
|
Text="{CompiledBinding Version}" />
|
||||||
|
|
||||||
<HyperlinkButton Grid.Row="1"
|
<HyperlinkButton Grid.Row="1"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
@ -46,19 +44,19 @@
|
|||||||
PolyForm Noncommercial License 1.0.0
|
PolyForm Noncommercial License 1.0.0
|
||||||
</HyperlinkButton>
|
</HyperlinkButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<!-- Main Content -->
|
||||||
<controls:Frame Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Name="Frame" IsNavigationStackEnabled="False" CacheSize="0">
|
<controls:Frame Grid.Row="1" Name="Frame" IsNavigationStackEnabled="False" CacheSize="0">
|
||||||
<controls:Frame.NavigationPageFactory>
|
<controls:Frame.NavigationPageFactory>
|
||||||
<ui:PageFactory/>
|
<ui:PageFactory/>
|
||||||
</controls:Frame.NavigationPageFactory>
|
</controls:Frame.NavigationPageFactory>
|
||||||
</controls:Frame>
|
</controls:Frame>
|
||||||
|
<!-- Buttons Panel -->
|
||||||
<Button Grid.Row="2" Grid.Column="0" Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding !Screen.ShowFinish}">Skip & close</Button>
|
<DockPanel Grid.Row="2" LastChildFill="False" IsVisible="{CompiledBinding !Screen.HideAllButtons}" HorizontalSpacing="10">
|
||||||
<StackPanel Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right" Orientation="Horizontal" Spacing="5" Margin="0 15 0 0">
|
<Button Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding !Screen.ShowFinish}" DockPanel.Dock="Left">Skip & close</Button>
|
||||||
<Button Command="{CompiledBinding Screen.GoBack}" IsEnabled="{CompiledBinding Screen.ShowGoBack}">Back</Button>
|
<Button Command="{CompiledBinding Screen.Continue}" IsVisible="{CompiledBinding !Screen.ShowFinish}" Width="80" DockPanel.Dock="Right">Continue</Button>
|
||||||
<Button Command="{CompiledBinding Screen.Continue}" IsVisible="{CompiledBinding !Screen.ShowFinish}" Width="80">Continue</Button>
|
<Button Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding Screen.ShowFinish}" Width="80" DockPanel.Dock="Right">Finish</Button>
|
||||||
<Button Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding Screen.ShowFinish}" Width="80">Finish</Button>
|
<Button Command="{CompiledBinding Screen.GoBack}" IsEnabled="{CompiledBinding Screen.ShowGoBack}" DockPanel.Dock="Right">Back</Button>
|
||||||
</StackPanel>
|
</DockPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
</Window>
|
</Window>
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.StartupWizard.Steps.DefaultEntriesStepView"
|
||||||
|
x:DataType="steps:DefaultEntriesStepViewModel">
|
||||||
|
<Panel>
|
||||||
|
<!-- Selection stage -->
|
||||||
|
<Grid RowDefinitions="Auto,*,Auto" IsVisible="{CompiledBinding CurrentEntry, Converter={x:Static ObjectConverters.IsNull}}">
|
||||||
|
<TextBlock Grid.Row="0" TextWrapping="Wrap" IsVisible="{CompiledBinding !FetchingDefaultEntries}">
|
||||||
|
Below is a list of default features that can be installed to get you started with Artemis. You can always install or uninstall features later via the Workshop.
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" IsVisible="{CompiledBinding FetchingDefaultEntries}" HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="10">
|
||||||
|
<ProgressBar IsIndeterminate="True" Width="400" />
|
||||||
|
<TextBlock TextAlignment="Center">Fetching default features from the Artemis workshop...</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Classes="card-title" IsVisible="{CompiledBinding DeviceProviderEntryViewModels.Count}">
|
||||||
|
Device providers
|
||||||
|
</TextBlock>
|
||||||
|
<ItemsControl ItemsSource="{CompiledBinding DeviceProviderEntryViewModels}" Margin="0,0,5,0">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<UniformGrid Columns="2" ColumnSpacing="5" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<TextBlock Classes="card-title" IsVisible="{CompiledBinding EssentialEntryViewModels.Count}">
|
||||||
|
Essentials
|
||||||
|
</TextBlock>
|
||||||
|
<ItemsControl ItemsSource="{CompiledBinding EssentialEntryViewModels}" Margin="0,0,5,0">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<UniformGrid Columns="2" ColumnSpacing="5" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<TextBlock Classes="card-title" IsVisible="{CompiledBinding OtherEntryViewModels.Count}">
|
||||||
|
Other features
|
||||||
|
</TextBlock>
|
||||||
|
<ItemsControl ItemsSource="{CompiledBinding OtherEntryViewModels}" Margin="0,0,5,0">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<UniformGrid Columns="2" ColumnSpacing="5" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Installation stage -->
|
||||||
|
<StackPanel Width="400" Margin="100 0 0 0" IsVisible="{CompiledBinding CurrentEntry, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||||
|
<Lottie Path="/Assets/Animations/nyan.json" RepeatCount="-1" Width="300" Height="300"></Lottie>
|
||||||
|
|
||||||
|
<ProgressBar Minimum="0"
|
||||||
|
Maximum="{CompiledBinding TotalEntries}"
|
||||||
|
Value="{CompiledBinding CurrentEntry.InstallProgress, FallbackValue=0}"
|
||||||
|
Margin="0 0 0 5" />
|
||||||
|
<TextBlock>
|
||||||
|
<Run>Currently installing: </Run>
|
||||||
|
<Run Text="{CompiledBinding CurrentEntry.Entry.Name}" FontWeight="Bold" />
|
||||||
|
</TextBlock>
|
||||||
|
<ProgressBar Minimum="0" Maximum="{CompiledBinding TotalEntries}" Value="{CompiledBinding InstalledEntries}" Margin="0 20 0 5" />
|
||||||
|
<TextBlock>
|
||||||
|
<Run Text="{CompiledBinding RemainingEntries}" FontWeight="Bold" />
|
||||||
|
<Run>feature(s) remaining, hold on tight!</Run>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Panel>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
||||||
|
|
||||||
|
public partial class DefaultEntriesStepView : ReactiveUserControl<DefaultEntriesStepViewModel>
|
||||||
|
{
|
||||||
|
public DefaultEntriesStepView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,135 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Extensions;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.UI.Shared.Utilities;
|
||||||
|
using Artemis.WebClient.Workshop;
|
||||||
|
using Artemis.WebClient.Workshop.Services;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
|
using ReactiveUI;
|
||||||
|
using StrawberryShake;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
||||||
|
|
||||||
|
public partial class DefaultEntriesStepViewModel : WizardStepViewModel
|
||||||
|
{
|
||||||
|
[Notify] private bool _fetchingDefaultEntries = true;
|
||||||
|
[Notify] private int _installedEntries;
|
||||||
|
[Notify] private int _totalEntries = 1;
|
||||||
|
[Notify] private DefaultEntryItemViewModel? _currentEntry;
|
||||||
|
|
||||||
|
private readonly IDeviceService _deviceService;
|
||||||
|
private readonly IWorkshopClient _client;
|
||||||
|
private readonly Func<IEntrySummary, DefaultEntryItemViewModel> _getDefaultEntryItemViewModel;
|
||||||
|
private readonly ObservableAsPropertyHelper<int> _remainingEntries;
|
||||||
|
|
||||||
|
public ObservableCollection<DefaultEntryItemViewModel> DeviceProviderEntryViewModels { get; } = [];
|
||||||
|
public ObservableCollection<DefaultEntryItemViewModel> EssentialEntryViewModels { get; } = [];
|
||||||
|
public ObservableCollection<DefaultEntryItemViewModel> OtherEntryViewModels { get; } = [];
|
||||||
|
public int RemainingEntries => _remainingEntries.Value;
|
||||||
|
|
||||||
|
public DefaultEntriesStepViewModel(IWorkshopService workshopService, IDeviceService deviceService, IWorkshopClient client,
|
||||||
|
Func<IEntrySummary, DefaultEntryItemViewModel> getDefaultEntryItemViewModel)
|
||||||
|
{
|
||||||
|
_deviceService = deviceService;
|
||||||
|
_client = client;
|
||||||
|
_getDefaultEntryItemViewModel = getDefaultEntryItemViewModel;
|
||||||
|
_remainingEntries = this.WhenAnyValue(vm => vm.InstalledEntries, vm => vm.TotalEntries)
|
||||||
|
.Select(t => t.Item2 - t.Item1 + 1)
|
||||||
|
.ToProperty(this, vm => vm.RemainingEntries);
|
||||||
|
|
||||||
|
ContinueText = "Install selected entries";
|
||||||
|
Continue = ReactiveCommand.CreateFromTask(async ct =>
|
||||||
|
{
|
||||||
|
await Install(ct);
|
||||||
|
ExecuteContinue();
|
||||||
|
}, this.WhenAnyValue(vm => vm.FetchingDefaultEntries).Select(b => !b));
|
||||||
|
GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen<WelcomeStepViewModel>());
|
||||||
|
|
||||||
|
this.WhenActivatedAsync(async d =>
|
||||||
|
{
|
||||||
|
CancellationToken ct = d.AsCancellationToken();
|
||||||
|
if (await workshopService.ValidateWorkshopStatus(false, ct))
|
||||||
|
await GetDefaultEntries(d.AsCancellationToken());
|
||||||
|
else if (!ct.IsCancellationRequested)
|
||||||
|
Wizard.ChangeScreen<WorkshopUnreachableStepViewModel>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteContinue()
|
||||||
|
{
|
||||||
|
// Without devices skip to the last step
|
||||||
|
if (_deviceService.EnabledDevices.Count == 0)
|
||||||
|
Wizard.ChangeScreen<SettingsStepViewModel>();
|
||||||
|
else
|
||||||
|
Wizard.ChangeScreen<LayoutsStepViewModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Install(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
List<DefaultEntryItemViewModel> entries =
|
||||||
|
[
|
||||||
|
..DeviceProviderEntryViewModels.Where(e => e.ShouldInstall && !e.IsInstalled),
|
||||||
|
..EssentialEntryViewModels.Where(e => e.ShouldInstall && !e.IsInstalled),
|
||||||
|
..OtherEntryViewModels.Where(e => e.ShouldInstall && !e.IsInstalled)
|
||||||
|
];
|
||||||
|
InstalledEntries = 0;
|
||||||
|
TotalEntries = entries.Count;
|
||||||
|
|
||||||
|
// Continue to the next screen if there are no entries to install
|
||||||
|
if (TotalEntries == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (DefaultEntryItemViewModel defaultEntryItemViewModel in entries)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
CurrentEntry = defaultEntryItemViewModel;
|
||||||
|
await defaultEntryItemViewModel.InstallEntry(cancellationToken);
|
||||||
|
InstalledEntries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(1000, cancellationToken);
|
||||||
|
CurrentEntry = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetDefaultEntries(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
FetchingDefaultEntries = true;
|
||||||
|
|
||||||
|
IOperationResult<IGetDefaultEntriesResult> result = await _client.GetDefaultEntries.ExecuteAsync(100, null, cancellationToken);
|
||||||
|
List<IEntrySummary> entries = result.Data?.EntriesV2?.Edges?.Select(e => e.Node).Cast<IEntrySummary>().ToList() ?? [];
|
||||||
|
while (result.Data?.EntriesV2?.PageInfo is {HasNextPage: true})
|
||||||
|
{
|
||||||
|
result = await _client.GetDefaultEntries.ExecuteAsync(100, result.Data.EntriesV2.PageInfo.EndCursor, cancellationToken);
|
||||||
|
if (result.Data?.EntriesV2?.Edges != null)
|
||||||
|
entries.AddRange(result.Data.EntriesV2.Edges.Select(e => e.Node));
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceProviderEntryViewModels.Clear();
|
||||||
|
EssentialEntryViewModels.Clear();
|
||||||
|
OtherEntryViewModels.Clear();
|
||||||
|
foreach (IEntrySummary entry in entries)
|
||||||
|
{
|
||||||
|
if (entry.DefaultEntryInfo == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
DefaultEntryItemViewModel viewModel = _getDefaultEntryItemViewModel(entry);
|
||||||
|
viewModel.ShouldInstall = entry.DefaultEntryInfo.IsEssential;
|
||||||
|
|
||||||
|
if (entry.DefaultEntryInfo.IsDeviceProvider)
|
||||||
|
DeviceProviderEntryViewModels.Add(viewModel);
|
||||||
|
else if (entry.DefaultEntryInfo.IsEssential)
|
||||||
|
EssentialEntryViewModels.Add(viewModel);
|
||||||
|
else
|
||||||
|
OtherEntryViewModels.Add(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchingDefaultEntries = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||||
|
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||||
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
|
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.StartupWizard.Steps.DefaultEntryItemView"
|
||||||
|
x:DataType="steps:DefaultEntryItemViewModel">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Border Classes="card" Padding="15" Margin="0 5" PointerPressed="HandlePointerPressed">
|
||||||
|
<Grid ColumnDefinitions="Auto,*,Auto,Auto" RowDefinitions="*, Auto">
|
||||||
|
<!-- Icon -->
|
||||||
|
<Border Grid.Column="0"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
CornerRadius="6"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0 0 10 0"
|
||||||
|
Width="80"
|
||||||
|
Height="80"
|
||||||
|
ClipToBounds="True">
|
||||||
|
<Image Stretch="UniformToFill" il:ImageLoader.Source="{CompiledBinding Entry.Id, Converter={StaticResource EntryIconUriConverter}, Mode=OneWay}" />
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<Grid Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto">
|
||||||
|
<StackPanel Grid.Row="0" Orientation="Horizontal">
|
||||||
|
<TextBlock Classes="h5" Margin="0 0 0 5" TextTrimming="CharacterEllipsis" Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock Grid.Row="1"
|
||||||
|
Classes="subtitle"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
Text="{CompiledBinding Entry.Summary, FallbackValue=Summary}" />
|
||||||
|
|
||||||
|
<ItemsControl Grid.Row="2" ItemsSource="{CompiledBinding Entry.Categories}" Margin="0 8 0 0">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Margin="0 0 3 0" />
|
||||||
|
<TextBlock Text="{CompiledBinding Name}" TextTrimming="CharacterEllipsis" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Management -->
|
||||||
|
<Border Grid.Column="3" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0" IsVisible="{CompiledBinding !IsInstalled}">
|
||||||
|
<StackPanel VerticalAlignment="Center">
|
||||||
|
<CheckBox MinHeight="26" Margin="0 4 0 0" IsChecked="{CompiledBinding ShouldInstall}">Install</CheckBox>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<Border Grid.Column="3" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0" IsVisible="{CompiledBinding IsInstalled}">
|
||||||
|
<StackPanel VerticalAlignment="Center">
|
||||||
|
<TextBlock>Already installed</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
||||||
|
|
||||||
|
public partial class DefaultEntryItemView : ReactiveUserControl<StartupWizard.Steps.DefaultEntryItemViewModel>
|
||||||
|
{
|
||||||
|
public DefaultEntryItemView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandlePointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ViewModel != null && !ViewModel.IsInstalled)
|
||||||
|
{
|
||||||
|
ViewModel.ShouldInstall = !ViewModel.ShouldInstall;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Exceptions;
|
||||||
|
using Artemis.UI.Screens.Plugins;
|
||||||
|
using Artemis.UI.Services;
|
||||||
|
using Artemis.UI.Services.Interfaces;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.UI.Shared.Utilities;
|
||||||
|
using Artemis.WebClient.Workshop;
|
||||||
|
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
||||||
|
using Artemis.WebClient.Workshop.Models;
|
||||||
|
using Artemis.WebClient.Workshop.Services;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
||||||
|
|
||||||
|
public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IWorkshopService _workshopService;
|
||||||
|
private readonly IWindowService _windowService;
|
||||||
|
private readonly IPluginManagementService _pluginManagementService;
|
||||||
|
private readonly IProfileService _profileService;
|
||||||
|
private readonly IPluginInteractionService _pluginInteractionService;
|
||||||
|
private readonly Progress<StreamProgress> _progress = new();
|
||||||
|
|
||||||
|
[Notify] private bool _isInstalled;
|
||||||
|
[Notify] private bool _shouldInstall;
|
||||||
|
[Notify] private float _installProgress;
|
||||||
|
|
||||||
|
public DefaultEntryItemViewModel(ILogger logger,
|
||||||
|
IEntrySummary entry,
|
||||||
|
IWorkshopService workshopService,
|
||||||
|
IWindowService windowService,
|
||||||
|
IPluginManagementService pluginManagementService,
|
||||||
|
IProfileService profileService,
|
||||||
|
IPluginInteractionService pluginInteractionService)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_workshopService = workshopService;
|
||||||
|
_windowService = windowService;
|
||||||
|
_pluginManagementService = pluginManagementService;
|
||||||
|
_profileService = profileService;
|
||||||
|
_pluginInteractionService = pluginInteractionService;
|
||||||
|
Entry = entry;
|
||||||
|
|
||||||
|
_progress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage;
|
||||||
|
this.WhenActivated((CompositeDisposable _) => { IsInstalled = workshopService.GetInstalledEntry(entry.Id) != null; });
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEntrySummary Entry { get; }
|
||||||
|
|
||||||
|
public async Task<bool> InstallEntry(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (IsInstalled || !ShouldInstall || Entry.LatestRelease == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
EntryInstallResult result = await _workshopService.InstallEntry(Entry, Entry.LatestRelease, _progress, cancellationToken);
|
||||||
|
|
||||||
|
if (!result.IsSuccess)
|
||||||
|
{
|
||||||
|
await _windowService.CreateContentDialog()
|
||||||
|
.WithTitle("Failed to install entry")
|
||||||
|
.WithContent($"Failed to install entry '{Entry.Name}' ({Entry.Id}): {result.Message}")
|
||||||
|
.WithCloseButtonText("Skip and continue")
|
||||||
|
.ShowAsync();
|
||||||
|
}
|
||||||
|
// If the entry is a plugin, enable the plugin and all features
|
||||||
|
else if (result.Entry?.EntryType == EntryType.Plugin)
|
||||||
|
await EnablePluginAndFeatures(result.Entry);
|
||||||
|
// If the entry is a profile, move it to the General profile category
|
||||||
|
else if (result.Entry?.EntryType == EntryType.Profile)
|
||||||
|
PrepareProfile(result.Entry);
|
||||||
|
|
||||||
|
return result.IsSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnablePluginAndFeatures(InstalledEntry entry)
|
||||||
|
{
|
||||||
|
if (!entry.TryGetMetadata("PluginId", out Guid pluginId))
|
||||||
|
throw new InvalidOperationException("Plugin entry does not contain a PluginId metadata value.");
|
||||||
|
|
||||||
|
Plugin? plugin = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginId);
|
||||||
|
if (plugin == null)
|
||||||
|
throw new InvalidOperationException($"Plugin with id '{pluginId}' does not exist.");
|
||||||
|
|
||||||
|
// There's quite a bit of UI involved in enabling a plugin, borrowing the PluginSettingsViewModel for this
|
||||||
|
await _pluginInteractionService.EnablePlugin(plugin, true);
|
||||||
|
|
||||||
|
// Find features without prerequisites to enable
|
||||||
|
foreach (PluginFeatureInfo pluginFeatureInfo in plugin.Features)
|
||||||
|
{
|
||||||
|
if (pluginFeatureInfo.Instance == null || pluginFeatureInfo.Instance.IsEnabled || pluginFeatureInfo.Prerequisites.Count != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pluginManagementService.EnablePluginFeature(pluginFeatureInfo.Instance, true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.Warning(e, "Failed to enable plugin feature '{FeatureName}', skipping", pluginFeatureInfo.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrepareProfile(InstalledEntry entry)
|
||||||
|
{
|
||||||
|
if (!entry.TryGetMetadata("ProfileId", out Guid profileId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProfileConfiguration? profile = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == profileId);
|
||||||
|
if (profile == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ProfileCategory category = _profileService.ProfileCategories.FirstOrDefault(c => c.Name == "General") ?? _profileService.CreateProfileCategory("General", true);
|
||||||
|
if (category.ProfileConfigurations.Contains(profile))
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
// Add the profile to the category
|
||||||
|
category.AddProfileConfiguration(profile, null);
|
||||||
|
|
||||||
|
// Suspend all but the first profile in the category
|
||||||
|
profile.IsSuspended = category.ProfileConfigurations.Count > 1;
|
||||||
|
|
||||||
|
_profileService.SaveProfileCategory(category);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,43 +0,0 @@
|
|||||||
<UserControl xmlns="https://github.com/avaloniaui"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
|
||||||
x:Class="Artemis.UI.Screens.StartupWizard.Steps.DevicesStepView"
|
|
||||||
x:DataType="steps:DevicesStepViewModel">
|
|
||||||
<Border Classes="card">
|
|
||||||
<Grid RowDefinitions="Auto,*,Auto,Auto">
|
|
||||||
<StackPanel Grid.Row="0">
|
|
||||||
<TextBlock TextWrapping="Wrap">
|
|
||||||
Devices are supported through the use of device providers.
|
|
||||||
</TextBlock>
|
|
||||||
<TextBlock TextWrapping="Wrap">
|
|
||||||
In the list below you can enable device providers for each brand you own by checking "Enable feature".
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<ScrollViewer Grid.Row="1" Margin="0 15">
|
|
||||||
<ItemsControl ItemsSource="{CompiledBinding DeviceProviders}" Margin="-4 0 8 0">
|
|
||||||
<ItemsControl.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<UniformGrid Columns="2" />
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ItemsControl.ItemsPanel>
|
|
||||||
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<Border Classes="card-condensed" Margin="4">
|
|
||||||
<ContentControl Content="{CompiledBinding}"></ContentControl>
|
|
||||||
</Border>
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
</ItemsControl>
|
|
||||||
</ScrollViewer>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="2" Foreground="#FFB9A40A" TextWrapping="Wrap">
|
|
||||||
Note: To avoid possible instability it's recommended to disable the device providers of brands you don't own.
|
|
||||||
</TextBlock>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</UserControl>
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
using ReactiveUI;
|
|
||||||
using System;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Linq;
|
|
||||||
using Artemis.Core;
|
|
||||||
using Artemis.Core.DeviceProviders;
|
|
||||||
using Artemis.Core.Services;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
|
||||||
|
|
||||||
public class DevicesStepViewModel : WizardStepViewModel
|
|
||||||
{
|
|
||||||
public DevicesStepViewModel(IPluginManagementService pluginManagementService, Func<PluginFeatureInfo, WizardPluginFeatureViewModel> getPluginFeatureViewModel, IDeviceService deviceService)
|
|
||||||
{
|
|
||||||
// Take all compatible device providers and create a view model for them
|
|
||||||
DeviceProviders = new ObservableCollection<WizardPluginFeatureViewModel>(pluginManagementService.GetAllPlugins()
|
|
||||||
.Where(p => p.Info.IsCompatible)
|
|
||||||
.SelectMany(p => p.Features.Where(f => f.FeatureType.IsAssignableTo(typeof(DeviceProvider))))
|
|
||||||
.OrderBy(f => f.Name)
|
|
||||||
.Select(getPluginFeatureViewModel));
|
|
||||||
|
|
||||||
Continue = ReactiveCommand.Create(() =>
|
|
||||||
{
|
|
||||||
if (deviceService.EnabledDevices.Count == 0)
|
|
||||||
Wizard.ChangeScreen<SettingsStepViewModel>();
|
|
||||||
else
|
|
||||||
Wizard.ChangeScreen<LayoutsStepViewModel>();
|
|
||||||
});
|
|
||||||
GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen<WelcomeStepViewModel>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableCollection<WizardPluginFeatureViewModel> DeviceProviders { get; }
|
|
||||||
}
|
|
||||||
@ -2,16 +2,14 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
|
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
|
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.StartupWizard.Steps.FinishStepView"
|
x:Class="Artemis.UI.Screens.StartupWizard.Steps.FinishStepView"
|
||||||
x:DataType="steps:FinishStepViewModel">
|
x:DataType="steps:FinishStepViewModel">
|
||||||
<Border Classes="card" VerticalAlignment="Top">
|
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Classes="h4">All finished!</TextBlock>
|
<TextBlock Classes="h4">All finished!</TextBlock>
|
||||||
|
<Border Classes="card" VerticalAlignment="Top">
|
||||||
|
<StackPanel>
|
||||||
<TextBlock TextWrapping="Wrap">
|
<TextBlock TextWrapping="Wrap">
|
||||||
You are now ready to start using Artemis, enjoy! 😁
|
You are now ready to start using Artemis, enjoy! 😁
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
@ -50,4 +48,5 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -2,31 +2,29 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
|
|
||||||
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
|
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.StartupWizard.Steps.LayoutsStepView"
|
x:Class="Artemis.UI.Screens.StartupWizard.Steps.LayoutsStepView"
|
||||||
x:DataType="steps:LayoutsStepViewModel">
|
x:DataType="steps:LayoutsStepViewModel">
|
||||||
<Border Classes="card">
|
<StackPanel Spacing="10">
|
||||||
<Grid RowDefinitions="Auto,Auto,*">
|
|
||||||
<StackPanel Grid.Row="0">
|
|
||||||
<TextBlock TextWrapping="Wrap">
|
<TextBlock TextWrapping="Wrap">
|
||||||
Device layouts provide Artemis with an image of your devices and exact LED positions. <LineBreak />
|
Device layouts provide Artemis with an image of your devices and exact LED positions. <LineBreak />
|
||||||
While not strictly necessary, this helps to create effects that are perfectly aligned with your hardware.
|
While not strictly necessary, this helps to create effects that are perfectly aligned with your hardware.
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock TextWrapping="Wrap" Margin="0 10">
|
<TextBlock TextWrapping="Wrap" Margin="0 10 0 0">
|
||||||
Below you can automatically search the Artemis Workshop for device layouts of your devices.
|
We've searched for layouts for your connected devices, you can see the results below. If one or more are missing consider creating your own for the best experience.
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
<Border Classes="card">
|
||||||
|
<StackPanel>
|
||||||
<Button Grid.Row="1"
|
<ScrollViewer>
|
||||||
Content="Auto-install layouts"
|
|
||||||
Command="{CompiledBinding LayoutFinderViewModel.SearchAll}"
|
|
||||||
ToolTip.Tip="Search layouts and if found install them automatically"
|
|
||||||
HorizontalAlignment="Right"/>
|
|
||||||
<ScrollViewer Grid.Row="2" Margin="0 15">
|
|
||||||
<ContentControl Content="{CompiledBinding LayoutFinderViewModel}"></ContentControl>
|
<ContentControl Content="{CompiledBinding LayoutFinderViewModel}"></ContentControl>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Grid>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
<HyperlinkButton
|
||||||
|
Content="Learn more about layouts on the wiki"
|
||||||
|
NavigateUri="https://wiki.artemis-rgb.com/en/guides/developer/layouts?mtm_campaign=artemis&mtm_kwd=wizard"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Bottom" />
|
||||||
|
</StackPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,4 +1,7 @@
|
|||||||
using Artemis.UI.Screens.Workshop.LayoutFinder;
|
using System;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using Artemis.UI.Screens.Workshop.LayoutFinder;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
||||||
@ -9,8 +12,10 @@ public class LayoutsStepViewModel : WizardStepViewModel
|
|||||||
{
|
{
|
||||||
LayoutFinderViewModel = layoutFinderViewModel;
|
LayoutFinderViewModel = layoutFinderViewModel;
|
||||||
|
|
||||||
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<SurfaceStepViewModel>());
|
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<SurfaceStepViewModel>(), LayoutFinderViewModel.SearchAll.IsExecuting.Select(isExecuting => !isExecuting));
|
||||||
GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen<DevicesStepViewModel>());
|
GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen<DefaultEntriesStepViewModel>(), LayoutFinderViewModel.SearchAll.IsExecuting.Select(isExecuting => !isExecuting));
|
||||||
|
|
||||||
|
LayoutFinderViewModel.WhenActivated((CompositeDisposable _) => LayoutFinderViewModel.SearchAll.Execute().Subscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
public LayoutFinderViewModel LayoutFinderViewModel { get; }
|
public LayoutFinderViewModel LayoutFinderViewModel { get; }
|
||||||
|
|||||||
@ -2,14 +2,12 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
|
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:behaviors="clr-namespace:Artemis.UI.Shared.Behaviors;assembly=Artemis.UI.Shared"
|
xmlns:behaviors="clr-namespace:Artemis.UI.Shared.Behaviors;assembly=Artemis.UI.Shared"
|
||||||
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
|
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.StartupWizard.Steps.SettingsStepView"
|
x:Class="Artemis.UI.Screens.StartupWizard.Steps.SettingsStepView"
|
||||||
x:DataType="steps:SettingsStepViewModel">
|
x:DataType="steps:SettingsStepViewModel">
|
||||||
<Border Classes="card" VerticalAlignment="Top">
|
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock>
|
<TextBlock>
|
||||||
@ -122,5 +120,4 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Border>
|
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -37,8 +37,9 @@ public class SettingsStepViewModel : WizardStepViewModel
|
|||||||
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<FinishStepViewModel>());
|
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<FinishStepViewModel>());
|
||||||
GoBack = ReactiveCommand.Create(() =>
|
GoBack = ReactiveCommand.Create(() =>
|
||||||
{
|
{
|
||||||
|
// Without devices, skip to the default entries screen
|
||||||
if (deviceService.EnabledDevices.Count == 0)
|
if (deviceService.EnabledDevices.Count == 0)
|
||||||
Wizard.ChangeScreen<DevicesStepViewModel>();
|
Wizard.ChangeScreen<DefaultEntriesStepViewModel>();
|
||||||
else
|
else
|
||||||
Wizard.ChangeScreen<SurfaceStepViewModel>();
|
Wizard.ChangeScreen<SurfaceStepViewModel>();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,16 +2,13 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
|
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
|
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.StartupWizard.Steps.SurfaceStepView"
|
x:Class="Artemis.UI.Screens.StartupWizard.Steps.SurfaceStepView"
|
||||||
x:DataType="steps:SurfaceStepViewModel">
|
x:DataType="steps:SurfaceStepViewModel">
|
||||||
|
|
||||||
<Grid RowDefinitions="Auto,*,Auto,Auto" ColumnDefinitions="*,*">
|
<Grid RowDefinitions="Auto,*,Auto,Auto" ColumnDefinitions="*,*">
|
||||||
<Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Classes="card">
|
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Spacing="10">
|
||||||
<StackPanel>
|
|
||||||
<TextBlock TextWrapping="Wrap">
|
<TextBlock TextWrapping="Wrap">
|
||||||
Artemis uses "spatial awareness" to create realistic effects across multiple devices.
|
Artemis uses "spatial awareness" to create realistic effects across multiple devices.
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
@ -19,7 +16,6 @@
|
|||||||
In order to do this correctly, we need to know where your devices are located on your desk. Select one of the two presets below, after the setup wizard finishes you can tweak this in detail in the surface editor.
|
In order to do this correctly, we need to know where your devices are located on your desk. Select one of the two presets below, after the setup wizard finishes you can tweak this in detail in the surface editor.
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
|
||||||
|
|
||||||
<Button Grid.Row="1"
|
<Button Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
@ -28,15 +24,14 @@
|
|||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
Margin="0 0 10 0"
|
Margin="0 0 10 0"
|
||||||
Width="280"
|
Width="280"
|
||||||
Height="280"
|
Height="280">
|
||||||
IsEnabled="False">
|
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<avalonia:MaterialIcon Kind="HandBackLeft" Width="150" Height="150" HorizontalAlignment="Center" />
|
<avalonia:MaterialIcon Kind="HandBackLeft" Width="150" Height="150" HorizontalAlignment="Center" />
|
||||||
<TextBlock TextAlignment="Center" Classes="h4" Margin="0 10 0 0">
|
<TextBlock TextAlignment="Center" Classes="h4" Margin="0 10 0 0">
|
||||||
Left-handed preset (NYI)
|
Left-handed preset
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock TextAlignment="Center" Classes="subtitle" TextWrapping="Wrap">
|
<TextBlock TextAlignment="Center" Classes="subtitle" TextWrapping="Wrap">
|
||||||
A preset with the mouse on the left side of the keyboard
|
A preset with the mouse on the left side of the keyboard (are you the 10%?)
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -27,9 +27,7 @@ public class SurfaceStepViewModel : WizardStepViewModel
|
|||||||
|
|
||||||
private void ExecuteSelectLayout(string layout)
|
private void ExecuteSelectLayout(string layout)
|
||||||
{
|
{
|
||||||
// TODO: Implement the layout
|
_deviceService.AutoArrangeDevices(layout == "left");
|
||||||
_deviceService.AutoArrangeDevices();
|
|
||||||
|
|
||||||
Wizard.ChangeScreen<SettingsStepViewModel>();
|
Wizard.ChangeScreen<SettingsStepViewModel>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6,19 +6,17 @@
|
|||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.StartupWizard.Steps.WelcomeStepView"
|
x:Class="Artemis.UI.Screens.StartupWizard.Steps.WelcomeStepView"
|
||||||
x:DataType="steps:WelcomeStepViewModel">
|
x:DataType="steps:WelcomeStepViewModel">
|
||||||
<Border Classes="card">
|
<StackPanel Margin="0 50 0 0">
|
||||||
<StackPanel>
|
<TextBlock Classes="h4" TextAlignment="Center">Welcome to the Artemis startup wizard!</TextBlock>
|
||||||
<TextBlock Classes="h4">Welcome to the Artemis startup wizard!</TextBlock>
|
|
||||||
|
|
||||||
<TextBlock TextWrapping="Wrap">
|
<TextBlock TextWrapping="Wrap" TextAlignment="Center">
|
||||||
In this wizard we'll walk you through the initial configuration of Artemis.
|
In this wizard we'll walk you through the initial configuration of Artemis.
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock TextWrapping="Wrap">
|
<TextBlock TextWrapping="Wrap" TextAlignment="Center">
|
||||||
Before you can start you need to tell Artemis which devices you want to use and where they are placed on your desk.
|
Before you can start you need to tell Artemis which devices you want to use and where they are placed on your desk.
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock Classes="subtitle" TextWrapping="Wrap" Margin="0 15 0 0">
|
<TextBlock Classes="subtitle" TextWrapping="Wrap" Margin="0 15 0 0" TextAlignment="Center">
|
||||||
PS: You can also skip the wizard and set things up yourself.
|
PS: You can also skip the wizard and set things up yourself.
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -6,7 +6,7 @@ public class WelcomeStepViewModel : WizardStepViewModel
|
|||||||
{
|
{
|
||||||
public WelcomeStepViewModel()
|
public WelcomeStepViewModel()
|
||||||
{
|
{
|
||||||
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<DevicesStepViewModel>());
|
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<DefaultEntriesStepViewModel>());
|
||||||
ShowGoBack = false;
|
ShowGoBack = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
|
||||||
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.StartupWizard.Steps.WorkshopUnreachableStepView"
|
||||||
|
x:DataType="steps:WorkshopUnreachableStepViewModel">
|
||||||
|
<StackPanel>
|
||||||
|
<StackPanel.Styles>
|
||||||
|
<Styles>
|
||||||
|
<Style Selector="TextBlock">
|
||||||
|
<Setter Property="TextAlignment" Value="Center"></Setter>
|
||||||
|
<Setter Property="TextWrapping" Value="Wrap"></Setter>
|
||||||
|
</Style>
|
||||||
|
</Styles>
|
||||||
|
</StackPanel.Styles>
|
||||||
|
|
||||||
|
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">Could not reach the workshop</TextBlock>
|
||||||
|
<TextBlock MaxWidth="600" Classes="subtitle">This unfortunately means the setup wizard cannot continue.</TextBlock>
|
||||||
|
<avalonia:MaterialIcon Kind="LanDisconnect" Width="120" Height="120" Margin="0 60"></avalonia:MaterialIcon>
|
||||||
|
|
||||||
|
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Please ensure you are connected to the internet.</TextBlock>
|
||||||
|
<TextBlock Margin="0 5" Classes="subtitle">If this keeps occuring, hit us up on Discord</TextBlock>
|
||||||
|
|
||||||
|
<Button HorizontalAlignment="Center" Margin="0 20" Command="{CompiledBinding Retry}">Retry</Button>
|
||||||
|
<Button HorizontalAlignment="Center" Command="{CompiledBinding Skip}">Try again on next launch</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
@ -4,11 +4,10 @@ using Avalonia.ReactiveUI;
|
|||||||
|
|
||||||
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
||||||
|
|
||||||
public partial class DevicesStepView : ReactiveUserControl<DevicesStepViewModel>
|
public partial class WorkshopUnreachableStepView : ReactiveUserControl<WorkshopUnreachableStepViewModel>
|
||||||
{
|
{
|
||||||
public DevicesStepView()
|
public WorkshopUnreachableStepView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
||||||
|
|
||||||
|
public class WorkshopUnreachableStepViewModel : WizardStepViewModel
|
||||||
|
{
|
||||||
|
private readonly ISettingsService _settingsService;
|
||||||
|
|
||||||
|
public WorkshopUnreachableStepViewModel(ISettingsService settingsService)
|
||||||
|
{
|
||||||
|
_settingsService = settingsService;
|
||||||
|
HideAllButtons = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Retry()
|
||||||
|
{
|
||||||
|
Wizard.ChangeScreen<DefaultEntriesStepViewModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Skip()
|
||||||
|
{
|
||||||
|
PluginSetting<bool> setting = _settingsService.GetSetting("UI.SetupWizardCompleted", false);
|
||||||
|
setting.Value = false;
|
||||||
|
setting.Save();
|
||||||
|
|
||||||
|
Wizard.Close(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,7 +4,6 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
|
xmlns:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.StartupWizard.WizardPluginFeatureView"
|
x:Class="Artemis.UI.Screens.StartupWizard.WizardPluginFeatureView"
|
||||||
|
|||||||
@ -15,6 +15,7 @@ public abstract partial class WizardStepViewModel : ValidatableViewModelBase
|
|||||||
[Notify] private bool _showFinish;
|
[Notify] private bool _showFinish;
|
||||||
[Notify] private bool _showGoBack = true;
|
[Notify] private bool _showGoBack = true;
|
||||||
[Notify] private bool _showHeader = true;
|
[Notify] private bool _showHeader = true;
|
||||||
|
[Notify] private bool _hideAllButtons;
|
||||||
|
|
||||||
public StartupWizardViewModel Wizard { get; set; } = null!;
|
public StartupWizardViewModel Wizard { get; set; } = null!;
|
||||||
}
|
}
|
||||||
@ -13,6 +13,7 @@ using Artemis.UI.Shared;
|
|||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
@ -180,11 +181,18 @@ public partial class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewMod
|
|||||||
|
|
||||||
private async Task ExecuteAutoArrange()
|
private async Task ExecuteAutoArrange()
|
||||||
{
|
{
|
||||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Auto-arrange layout", "Are you sure you want to auto-arrange your layout? Your current settings will be overwritten.");
|
ContentDialogResult contentDialogResult = await _windowService.CreateContentDialog()
|
||||||
if (!confirmed)
|
.WithTitle("Auto-arrange layout")
|
||||||
return;
|
.WithContent("Which preset would you like to apply? Your current settings will be overwritten.")
|
||||||
|
.HavingPrimaryButton(b => b.WithText("Left-handed preset"))
|
||||||
|
.HavingSecondaryButton(b => b.WithText("Right-handed preset"))
|
||||||
|
.WithCloseButtonText("Cancel")
|
||||||
|
.ShowAsync();
|
||||||
|
|
||||||
_deviceService.AutoArrangeDevices();
|
if (contentDialogResult == ContentDialogResult.Primary)
|
||||||
|
_deviceService.AutoArrangeDevices(true);
|
||||||
|
else if (contentDialogResult == ContentDialogResult.Secondary)
|
||||||
|
_deviceService.AutoArrangeDevices(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderServiceOnFrameRendering(object? sender, FrameRenderingEventArgs e)
|
private void RenderServiceOnFrameRendering(object? sender, FrameRenderingEventArgs e)
|
||||||
|
|||||||
@ -33,16 +33,17 @@ public partial class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
|
|||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
ViewModel!.AutoFitRequested += ViewModelOnAutoFitRequested;
|
NodeScriptViewModel vm = ViewModel!;
|
||||||
ViewModel.PickerPositionSubject.Subscribe(ShowPickerAt).DisposeWith(d);
|
vm.AutoFitRequested += ViewModelOnAutoFitRequested;
|
||||||
if (ViewModel.IsPreview)
|
vm.PickerPositionSubject.Subscribe(ShowPickerAt).DisposeWith(d);
|
||||||
|
if (vm.IsPreview)
|
||||||
{
|
{
|
||||||
BoundsProperty.Changed.Subscribe(BoundsPropertyChanged).DisposeWith(d);
|
BoundsProperty.Changed.Subscribe(BoundsPropertyChanged).DisposeWith(d);
|
||||||
ViewModel.NodeViewModels.ToObservableChangeSet().Subscribe(_ => AutoFitIfPreview()).DisposeWith(d);
|
vm.NodeViewModels.ToObservableChangeSet().Subscribe(_ => AutoFitIfPreview()).DisposeWith(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() => AutoFit(true), DispatcherPriority.ContextIdle);
|
Dispatcher.UIThread.InvokeAsync(() => AutoFit(true), DispatcherPriority.ContextIdle);
|
||||||
Disposable.Create(() => ViewModel.AutoFitRequested -= ViewModelOnAutoFitRequested).DisposeWith(d);
|
Disposable.Create(() => vm.AutoFitRequested -= ViewModelOnAutoFitRequested).DisposeWith(d);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,11 +3,8 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting"
|
xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
|
||||||
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia"
|
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia"
|
||||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.VisualScripting.NodeScriptWindowView"
|
x:Class="Artemis.UI.Screens.VisualScripting.NodeScriptWindowView"
|
||||||
x:DataType="visualScripting:NodeScriptWindowViewModel"
|
x:DataType="visualScripting:NodeScriptWindowViewModel"
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting"
|
xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="250" d:DesignHeight="150"
|
mc:Ignorable="d" d:DesignWidth="250" d:DesignHeight="150"
|
||||||
x:Class="Artemis.UI.Screens.VisualScripting.NodeView"
|
x:Class="Artemis.UI.Screens.VisualScripting.NodeView"
|
||||||
x:DataType="visualScripting:NodeViewModel">
|
x:DataType="visualScripting:NodeViewModel">
|
||||||
|
|||||||
@ -18,10 +18,10 @@ public class CategoriesViewModel : ActivatableViewModelBase
|
|||||||
{
|
{
|
||||||
private ObservableAsPropertyHelper<IReadOnlyList<EntryFilterInput>?>? _categoryFilters;
|
private ObservableAsPropertyHelper<IReadOnlyList<EntryFilterInput>?>? _categoryFilters;
|
||||||
|
|
||||||
public CategoriesViewModel(IWorkshopClient client)
|
public CategoriesViewModel(EntryType entryType, IWorkshopClient client)
|
||||||
{
|
{
|
||||||
client.GetCategories
|
client.GetCategories
|
||||||
.Watch(ExecutionStrategy.CacheFirst)
|
.Watch(entryType, ExecutionStrategy.CacheFirst)
|
||||||
.SelectOperationResult(c => c.Categories)
|
.SelectOperationResult(c => c.Categories)
|
||||||
.ToObservableChangeSet(c => c.Id)
|
.ToObservableChangeSet(c => c.Id)
|
||||||
.Transform(c => new CategoryViewModel(c))
|
.Transform(c => new CategoryViewModel(c))
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:currentUser="clr-namespace:Artemis.UI.Screens.Workshop.CurrentUser"
|
xmlns:currentUser="clr-namespace:Artemis.UI.Screens.Workshop.CurrentUser"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Workshop.CurrentUser.CurrentUserView"
|
x:Class="Artemis.UI.Screens.Workshop.CurrentUser.CurrentUserView"
|
||||||
|
|||||||
@ -51,11 +51,11 @@
|
|||||||
ToolTip.Tip="Click to browse">
|
ToolTip.Tip="Click to browse">
|
||||||
</Button>
|
</Button>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
</Border>
|
</Border>
|
||||||
<TextBlock Foreground="{DynamicResource SystemFillColorCriticalBrush}" Margin="2 0" IsVisible="{CompiledBinding !IconValid}" TextWrapping="Wrap">
|
<TextBlock Foreground="{DynamicResource SystemFillColorCriticalBrush}" Margin="2 0" IsVisible="{CompiledBinding !IconValid}" TextWrapping="Wrap">
|
||||||
Icon required
|
Icon required
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
<CheckBox IsChecked="{CompiledBinding Fit}">Shrink</CheckBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Grid.Column="1">
|
<StackPanel Grid.Column="1">
|
||||||
<Label Target="Name" Margin="0">Name</Label>
|
<Label Target="Name" Margin="0">Name</Label>
|
||||||
@ -64,7 +64,11 @@
|
|||||||
<Label Target="Summary" Margin="0 5 0 0">Summary</Label>
|
<Label Target="Summary" Margin="0 5 0 0">Summary</Label>
|
||||||
<TextBox Name="Summary" Text="{CompiledBinding Summary}"></TextBox>
|
<TextBox Name="Summary" Text="{CompiledBinding Summary}"></TextBox>
|
||||||
|
|
||||||
<CheckBox IsVisible="{CompiledBinding IsAdministrator}" IsChecked="{CompiledBinding IsDefault}">Download by default (admin only)</CheckBox>
|
<StackPanel IsVisible="{CompiledBinding IsAdministrator}" Orientation="Horizontal">
|
||||||
|
<CheckBox IsChecked="{CompiledBinding IsDefault}">Download by default (admin only)</CheckBox>
|
||||||
|
<CheckBox IsEnabled="{CompiledBinding IsDefault}" IsChecked="{CompiledBinding IsEssential}">Essential</CheckBox>
|
||||||
|
<CheckBox IsEnabled="{CompiledBinding IsDefault}" IsChecked="{CompiledBinding IsDeviceProvider}">Device provider</CheckBox>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@ -36,9 +36,14 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
|
|||||||
[Notify] private string _summary = string.Empty;
|
[Notify] private string _summary = string.Empty;
|
||||||
[Notify] private string _description = string.Empty;
|
[Notify] private string _description = string.Empty;
|
||||||
[Notify] private bool _isDefault;
|
[Notify] private bool _isDefault;
|
||||||
|
[Notify] private bool _isEssential;
|
||||||
|
[Notify] private bool _isDeviceProvider;
|
||||||
|
[Notify] private bool _fit;
|
||||||
[Notify] private Bitmap? _iconBitmap;
|
[Notify] private Bitmap? _iconBitmap;
|
||||||
[Notify(Setter.Private)] private bool _iconChanged;
|
[Notify(Setter.Private)] private bool _iconChanged;
|
||||||
|
|
||||||
|
private string? _lastIconPath;
|
||||||
|
|
||||||
public EntrySpecificationsViewModel(IWorkshopClient workshopClient, IWindowService windowService, IAuthenticationService authenticationService)
|
public EntrySpecificationsViewModel(IWorkshopClient workshopClient, IWindowService windowService, IAuthenticationService authenticationService)
|
||||||
{
|
{
|
||||||
_workshopClient = workshopClient;
|
_workshopClient = workshopClient;
|
||||||
@ -66,8 +71,9 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
|
|||||||
_categoriesValid = categoriesRule.ValidationChanged.Select(c => c.IsValid).ToProperty(this, vm => vm.CategoriesValid);
|
_categoriesValid = categoriesRule.ValidationChanged.Select(c => c.IsValid).ToProperty(this, vm => vm.CategoriesValid);
|
||||||
_descriptionValid = descriptionRule.ValidationChanged.Select(c => c.IsValid).ToProperty(this, vm => vm.DescriptionValid);
|
_descriptionValid = descriptionRule.ValidationChanged.Select(c => c.IsValid).ToProperty(this, vm => vm.DescriptionValid);
|
||||||
|
|
||||||
this.WhenActivatedAsync(async _ => await PopulateCategories());
|
|
||||||
IsAdministrator = authenticationService.GetRoles().Contains("Administrator");
|
IsAdministrator = authenticationService.GetRoles().Contains("Administrator");
|
||||||
|
this.WhenActivatedAsync(async _ => await PopulateCategories());
|
||||||
|
this.WhenAnyValue(vm => vm.Fit).Subscribe(_ => UpdateIcon());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> SelectIcon { get; }
|
public ReactiveCommand<Unit, Unit> SelectIcon { get; }
|
||||||
@ -82,6 +88,7 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
|
|||||||
public bool IsAdministrator { get; }
|
public bool IsAdministrator { get; }
|
||||||
|
|
||||||
public List<long> PreselectedCategories { get; set; } = new();
|
public List<long> PreselectedCategories { get; set; } = new();
|
||||||
|
public EntryType EntryType { get; set; }
|
||||||
|
|
||||||
private async Task ExecuteSelectIcon()
|
private async Task ExecuteSelectIcon()
|
||||||
{
|
{
|
||||||
@ -92,14 +99,23 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
|
|||||||
if (result == null)
|
if (result == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_lastIconPath = result[0];
|
||||||
|
UpdateIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateIcon()
|
||||||
|
{
|
||||||
|
if (_lastIconPath == null)
|
||||||
|
return;
|
||||||
|
|
||||||
IconBitmap?.Dispose();
|
IconBitmap?.Dispose();
|
||||||
IconBitmap = BitmapExtensions.LoadAndResize(result[0], 128);
|
IconBitmap = BitmapExtensions.LoadAndResize(_lastIconPath, 128, Fit);
|
||||||
IconChanged = true;
|
IconChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PopulateCategories()
|
private async Task PopulateCategories()
|
||||||
{
|
{
|
||||||
IOperationResult<IGetCategoriesResult> categories = await _workshopClient.GetCategories.ExecuteAsync();
|
IOperationResult<IGetCategoriesResult> categories = await _workshopClient.GetCategories.ExecuteAsync(EntryType);
|
||||||
Categories.Clear();
|
Categories.Clear();
|
||||||
if (categories.Data != null)
|
if (categories.Data != null)
|
||||||
Categories.AddRange(categories.Data.Categories.Select(c => new CategoryViewModel(c) {IsSelected = PreselectedCategories.Contains(c.Id)}));
|
Categories.AddRange(categories.Data.Categories.Select(c => new CategoryViewModel(c) {IsSelected = PreselectedCategories.Contains(c.Id)}));
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user