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>
|
||||
/// The full path to the Artemis data folder
|
||||
/// </summary>
|
||||
#if DEBUG
|
||||
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis-dev");
|
||||
#else
|
||||
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis");
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the Artemis logs folder
|
||||
@ -140,6 +144,11 @@ public static class Constants
|
||||
/// Gets the startup arguments provided to the application
|
||||
/// </summary>
|
||||
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 EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};
|
||||
|
||||
@ -11,4 +11,10 @@ public interface IPluginConfigurationDialog
|
||||
/// The type of view model the tab contains
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
// Initialize the services
|
||||
_pluginManagementService.CopyBuiltInPlugins();
|
||||
_pluginManagementService.LoadPlugins(IsElevated);
|
||||
_pluginManagementService.StartHotReload();
|
||||
_renderService.Initialize();
|
||||
|
||||
@ -157,10 +157,11 @@ internal class DeviceService : IDeviceService
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="leftHanded"></param>
|
||||
/// <inheritdoc />
|
||||
public void AutoArrangeDevices()
|
||||
public void AutoArrangeDevices(bool leftHanded)
|
||||
{
|
||||
SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement();
|
||||
SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement(leftHanded);
|
||||
surfaceArrangement.Arrange(_devices);
|
||||
foreach (ArtemisDevice artemisDevice in _devices)
|
||||
artemisDevice.ApplyDefaultCategories();
|
||||
|
||||
@ -46,7 +46,8 @@ public interface IDeviceService : IArtemisService
|
||||
/// <summary>
|
||||
/// Applies auto-arranging logic to the surface
|
||||
/// </summary>
|
||||
void AutoArrangeDevices();
|
||||
/// <param name="leftHanded"></param>
|
||||
void AutoArrangeDevices(bool leftHanded);
|
||||
|
||||
/// <summary>
|
||||
/// Apples the best available to the provided <see cref="ArtemisDevice" />
|
||||
|
||||
@ -21,13 +21,12 @@ public interface IPluginManagementService : IArtemisService, IDisposable
|
||||
/// Indicates whether or not plugins are currently being loaded
|
||||
/// </summary>
|
||||
bool LoadingPlugins { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Copy built-in plugins from the executable directory to the plugins directory if the version is higher
|
||||
/// (higher or equal if compiled as debug)
|
||||
/// Indicates whether or not plugins are currently loaded
|
||||
/// </summary>
|
||||
void CopyBuiltInPlugins();
|
||||
|
||||
bool LoadedPlugins { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads all installed plugins. If plugins already loaded this will reload them all
|
||||
/// </summary>
|
||||
@ -150,12 +149,7 @@ public interface IPluginManagementService : IArtemisService, IDisposable
|
||||
/// <param name="device"></param>
|
||||
/// <returns></returns>
|
||||
DeviceProvider GetDeviceProviderByDevice(IRGBDevice device);
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when built-in plugins are being loaded
|
||||
/// </summary>
|
||||
event EventHandler CopyingBuildInPlugins;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a plugin has started loading
|
||||
/// </summary>
|
||||
|
||||
@ -46,114 +46,8 @@ internal class PluginManagementService : IPluginManagementService
|
||||
public List<DirectoryInfo> AdditionalPluginDirectories { get; } = new();
|
||||
|
||||
public bool LoadingPlugins { 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 bool LoadedPlugins { get; private set; }
|
||||
|
||||
public List<Plugin> GetAllPlugins()
|
||||
{
|
||||
@ -328,8 +222,10 @@ internal class PluginManagementService : IPluginManagementService
|
||||
// ReSharper restore InconsistentlySynchronizedField
|
||||
|
||||
LoadingPlugins = false;
|
||||
LoadedPlugins = true;
|
||||
}
|
||||
|
||||
|
||||
public void UnloadPlugins()
|
||||
{
|
||||
// Unload all plugins
|
||||
@ -686,7 +582,7 @@ internal class PluginManagementService : IPluginManagementService
|
||||
|
||||
if (removeSettings)
|
||||
RemovePluginSettings(plugin);
|
||||
|
||||
|
||||
OnPluginRemoved(new PluginEventArgs(plugin));
|
||||
}
|
||||
|
||||
@ -893,7 +789,7 @@ internal class PluginManagementService : IPluginManagementService
|
||||
{
|
||||
PluginDisabled?.Invoke(this, e);
|
||||
}
|
||||
|
||||
|
||||
protected virtual void OnPluginRemoved(PluginEventArgs e)
|
||||
{
|
||||
PluginRemoved?.Invoke(this, e);
|
||||
|
||||
@ -48,22 +48,42 @@ internal class SurfaceArrangement
|
||||
}
|
||||
}
|
||||
|
||||
internal static SurfaceArrangement GetDefaultArrangement()
|
||||
internal static SurfaceArrangement GetDefaultArrangement(bool leftHanded)
|
||||
{
|
||||
SurfaceArrangement arrangement = new();
|
||||
|
||||
SurfaceArrangementType keypad = arrangement.AddType(RGBDeviceType.Keypad, 1);
|
||||
keypad.AddConfiguration(new SurfaceArrangementConfiguration(null, HorizontalArrangementPosition.Equal, VerticalArrangementPosition.Equal, 20));
|
||||
SurfaceArrangementType keyboard, keypad, mousepad, mouse;
|
||||
if (leftHanded)
|
||||
{
|
||||
mousepad = arrangement.AddType(RGBDeviceType.Mousepad, 1);
|
||||
mousepad.AddConfiguration(new SurfaceArrangementConfiguration(null, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 10));
|
||||
|
||||
SurfaceArrangementType keyboard = arrangement.AddType(RGBDeviceType.Keyboard, 1);
|
||||
keyboard.AddConfiguration(new SurfaceArrangementConfiguration(keypad, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 20));
|
||||
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));
|
||||
|
||||
SurfaceArrangementType mousepad = arrangement.AddType(RGBDeviceType.Mousepad, 1);
|
||||
mousepad.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 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));
|
||||
|
||||
SurfaceArrangementType mouse = arrangement.AddType(RGBDeviceType.Mouse, 2);
|
||||
mouse.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
|
||||
mouse.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Center, 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));
|
||||
|
||||
keyboard = arrangement.AddType(RGBDeviceType.Keyboard, 1);
|
||||
keyboard.AddConfiguration(new SurfaceArrangementConfiguration(keypad, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 20));
|
||||
|
||||
mousepad = arrangement.AddType(RGBDeviceType.Mousepad, 1);
|
||||
mousepad.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, 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(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Center, 100));
|
||||
}
|
||||
|
||||
SurfaceArrangementType headset = arrangement.AddType(RGBDeviceType.Headset, 1);
|
||||
headset.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Bottom, 100));
|
||||
|
||||
@ -32,7 +32,7 @@ internal class SurfaceArrangementConfiguration
|
||||
public int MarginBottom { get; }
|
||||
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))
|
||||
return false;
|
||||
@ -42,10 +42,10 @@ internal class SurfaceArrangementConfiguration
|
||||
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
|
||||
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;
|
||||
foreach (ArtemisDevice artemisDevice in devices)
|
||||
foreach (ArtemisDevice artemisDevice in devicesToArrange)
|
||||
{
|
||||
if (previous != null)
|
||||
{
|
||||
|
||||
@ -28,18 +28,18 @@ internal class SurfaceArrangementType
|
||||
|
||||
public void Arrange(List<ArtemisDevice> devices)
|
||||
{
|
||||
devices = devices.Where(d => d.DeviceType == DeviceType).ToList();
|
||||
if (!devices.Any())
|
||||
List<ArtemisDevice> devicesToArrange = devices.Where(d => d.DeviceType == DeviceType).ToList();
|
||||
if (!devicesToArrange.Any())
|
||||
return;
|
||||
|
||||
AppliedConfiguration = null;
|
||||
foreach (SurfaceArrangementConfiguration configuration in Configurations)
|
||||
{
|
||||
bool applied = configuration.Apply(devices);
|
||||
bool applied = configuration.Apply(devicesToArrange, devices);
|
||||
if (applied)
|
||||
{
|
||||
AppliedConfiguration = configuration;
|
||||
foreach (ArtemisDevice artemisDevice in devices)
|
||||
foreach (ArtemisDevice artemisDevice in devicesToArrange)
|
||||
artemisDevice.ZIndex = ZIndex;
|
||||
return;
|
||||
}
|
||||
@ -52,7 +52,7 @@ internal class SurfaceArrangementType
|
||||
VerticalArrangementPosition.Equal,
|
||||
10
|
||||
) {SurfaceArrangement = SurfaceArrangement};
|
||||
fallback.Apply(devices);
|
||||
fallback.Apply(devicesToArrange, devices);
|
||||
AppliedConfiguration = fallback;
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@ using Avalonia.Controls;
|
||||
using Avalonia.Controls.Documents;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.LogicalTree;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
@ -43,7 +42,7 @@ public partial class ProfileConfigurationIcon : UserControl, IDisposable
|
||||
if (ConfigurationIcon.IconType == ProfileConfigurationIconType.MaterialIcon)
|
||||
{
|
||||
Content = Enum.TryParse(ConfigurationIcon.IconName, true, out MaterialIconKind parsedIcon)
|
||||
? new MaterialIcon {Kind = parsedIcon!}
|
||||
? new MaterialIcon {Kind = parsedIcon}
|
||||
: new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
||||
}
|
||||
else if (ConfigurationIcon.IconBytes != null)
|
||||
@ -65,19 +64,28 @@ public partial class ProfileConfigurationIcon : UserControl, IDisposable
|
||||
return;
|
||||
|
||||
_stream = new MemoryStream(ConfigurationIcon.IconBytes);
|
||||
if (!ConfigurationIcon.Fill)
|
||||
Border border = new()
|
||||
{
|
||||
Content = new Image {Source = new Bitmap(_stream)};
|
||||
return;
|
||||
CornerRadius = CornerRadius,
|
||||
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
|
||||
{
|
||||
Background = TextElement.GetForeground(this),
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
OpacityMask = new ImageBrush(new Bitmap(_stream))
|
||||
};
|
||||
Content = border;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:tagsInput="clr-namespace:Artemis.UI.Shared.TagsInput"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
x:CompileBindings="True">
|
||||
|
||||
@ -6,6 +6,22 @@ namespace Artemis.UI.Shared;
|
||||
/// <inheritdoc />
|
||||
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 />
|
||||
public override Type Type => typeof(T);
|
||||
}
|
||||
@ -17,4 +33,7 @@ public abstract class PluginConfigurationDialog : IPluginConfigurationDialog
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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)
|
||||
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;
|
||||
|
||||
string? previousPath = _currentRouteSubject.Value;
|
||||
@ -128,12 +130,8 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
|
||||
await navigation.Navigate(args);
|
||||
|
||||
// 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)
|
||||
{
|
||||
await Reload();
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.AddToHistory && previousPath != null)
|
||||
{
|
||||
@ -251,7 +249,13 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
|
||||
if (_previousWindowRoute != null && _currentRouteSubject.Value == "blank")
|
||||
Dispatcher.UIThread.InvokeAsync(async () => await Navigate(_previousWindowRoute, new RouterNavigationOptions {AddToHistory = false, EnableLogging = false}));
|
||||
else if (_currentRouteSubject.Value == null || _currentRouteSubject.Value == "blank")
|
||||
Dispatcher.UIThread.InvokeAsync(async () => await Navigate("home", new RouterNavigationOptions {AddToHistory = false, EnableLogging = true}));
|
||||
{
|
||||
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}));
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindowServiceOnMainWindowClosed(object? sender, EventArgs e)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Avalonia.Controls;
|
||||
|
||||
@ -185,12 +185,15 @@ internal class ProfileEditorService : IProfileEditorService
|
||||
{
|
||||
// Activate the profile if one was provided
|
||||
if (profileConfiguration != null)
|
||||
{
|
||||
_profileService.FocusProfile = profileConfiguration;
|
||||
_profileService.ActivateProfile(profileConfiguration);
|
||||
}
|
||||
|
||||
// If there is no profile configuration or module, deliberately set the override to null
|
||||
_moduleService.SetActivationOverride(profileConfiguration?.Module);
|
||||
});
|
||||
|
||||
_profileService.FocusProfile = profileConfiguration;
|
||||
|
||||
_profileConfigurationSubject.OnNext(profileConfiguration);
|
||||
|
||||
ChangeTime(TimeSpan.Zero);
|
||||
|
||||
@ -8,6 +8,7 @@ using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Threading;
|
||||
using DryIoc;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
|
||||
|
||||
namespace Artemis.UI.Shared.Services;
|
||||
|
||||
@ -94,6 +95,7 @@ internal class WindowService : IWindowService
|
||||
.WithTitle(title)
|
||||
.WithContent(message)
|
||||
.HavingPrimaryButton(b => b.WithText(confirm))
|
||||
.WithDefaultButton(ContentDialogButton.Primary)
|
||||
.WithCloseButtonText(cancel)
|
||||
.ShowAsync();
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
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">
|
||||
<Design.PreviewWith>
|
||||
<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"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia">
|
||||
<!-- Preview -->
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20">
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:dataModel="clr-namespace:Artemis.UI.Shared.DataModelVisualization.Shared"
|
||||
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">
|
||||
<Design.PreviewWith>
|
||||
<dataModelPicker:DataModelPicker />
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
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:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker">
|
||||
<Design.PreviewWith>
|
||||
|
||||
@ -16,6 +16,7 @@ using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
using DryIoc;
|
||||
using HotAvalonia;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Windows;
|
||||
@ -40,6 +41,7 @@ public class App : Application
|
||||
LegacyMigrationService.MigrateToSqlite(_container);
|
||||
|
||||
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
|
||||
this.EnableHotReload();
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
@ -89,7 +91,7 @@ public class App : Application
|
||||
try
|
||||
{
|
||||
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.EnsureSuccessStatusCode();
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
<AssemblyTitle>Artemis</AssemblyTitle>
|
||||
<ApplicationIcon>..\Artemis.UI\Assets\Images\Logo\application.ico</ApplicationIcon>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<HotAvaloniaAutoEnable>false</HotAvaloniaAutoEnable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
|
||||
@ -2,6 +2,7 @@ using System;
|
||||
using Artemis.Core;
|
||||
using Artemis.Storage;
|
||||
using Avalonia;
|
||||
using Avalonia.Logging;
|
||||
using Avalonia.ReactiveUI;
|
||||
using DryIoc;
|
||||
using Serilog;
|
||||
@ -33,10 +34,7 @@ internal class Program
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
{
|
||||
return AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.LogToTrace()
|
||||
.UseReactiveUI();
|
||||
return AppBuilder.Configure<App>().UsePlatformDetect().LogToTrace().UseReactiveUI();
|
||||
}
|
||||
|
||||
public static void CreateLogger(IContainer container)
|
||||
|
||||
@ -37,38 +37,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Screens\Workshop\Plugin\Dialogs\DeviceProviderPickerDialogView.axaml.cs">
|
||||
<DependentUpon>DeviceProviderPickerDialogView.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>
|
||||
<Compile Update="Screens\StartupWizard\Steps\WorkshopUnreachableStepView.axaml.cs">
|
||||
<DependentUpon>WorkshopUnreachableStepView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Remove="Screens\Workshop\Entries\Tabs\PluginListView.axaml" />
|
||||
<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" />
|
||||
<Folder Include="Screens\Profiles\" />
|
||||
</ItemGroup>
|
||||
</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"
|
||||
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<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">
|
||||
<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_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_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_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_005Csidebar_005Ccontentdialogs/@EntryIndexedValue">True</s:Boolean>
|
||||
<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:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Controls.SplitMarkdownEditor">
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||
using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
|
||||
using Avalonia;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
using Artemis.UI.Shared.Services.PropertyInput;
|
||||
using Avalonia.Threading;
|
||||
using ReactiveUI;
|
||||
using LayerBrushPresetViewModel = Artemis.UI.Screens.ProfileEditor.Properties.Tree.Dialogs.LayerBrushPresetViewModel;
|
||||
|
||||
namespace Artemis.UI.DefaultTypes.PropertyInput;
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media.Imaging;
|
||||
using SkiaSharp;
|
||||
|
||||
@ -8,41 +7,75 @@ namespace Artemis.UI.Extensions;
|
||||
|
||||
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);
|
||||
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);
|
||||
using MemoryStream copy = new();
|
||||
stream.CopyTo(copy);
|
||||
copy.Seek(0, SeekOrigin.Begin);
|
||||
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)
|
||||
{
|
||||
// Get smaller dimension.
|
||||
int minDim = Math.Min(source.Width, source.Height);
|
||||
if (!fit)
|
||||
{
|
||||
// Get smaller dimension.
|
||||
int minDim = Math.Min(source.Width, source.Height);
|
||||
|
||||
// Calculate crop rectangle position for center crop.
|
||||
int deltaX = (source.Width - minDim) / 2;
|
||||
int deltaY = (source.Height - minDim) / 2;
|
||||
// Calculate crop rectangle position for center crop.
|
||||
int deltaX = (source.Width - minDim) / 2;
|
||||
int deltaY = (source.Height - minDim) / 2;
|
||||
|
||||
// Create crop rectangle.
|
||||
SKRectI rect = new(deltaX, deltaY, deltaX + minDim, deltaY + minDim);
|
||||
// Create crop rectangle.
|
||||
SKRectI rect = new(deltaX, deltaY, deltaX + minDim, deltaY + minDim);
|
||||
|
||||
// Do the actual cropping of the bitmap.
|
||||
using SKBitmap croppedBitmap = new(minDim, minDim);
|
||||
source.ExtractSubset(croppedBitmap, rect);
|
||||
// Do the actual cropping of the bitmap.
|
||||
using SKBitmap croppedBitmap = new(minDim, minDim);
|
||||
source.ExtractSubset(croppedBitmap, rect);
|
||||
|
||||
// Resize to the desired size after cropping.
|
||||
using SKBitmap resizedBitmap = croppedBitmap.Resize(new SKImageInfo(size, size), SKFilterQuality.High);
|
||||
// Resize to the desired size after cropping.
|
||||
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.Profile;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using PluginDetailsViewModel = Artemis.UI.Screens.Workshop.Plugins.PluginDetailsViewModel;
|
||||
|
||||
namespace Artemis.UI.Routing
|
||||
{
|
||||
@ -65,7 +64,10 @@ namespace Artemis.UI.Routing
|
||||
new RouteRegistration<AccountTabViewModel>("account"),
|
||||
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 =>
|
||||
{
|
||||
Observable.FromEventPattern(x => ViewModel!.ActivationRequested += x, x => ViewModel!.ActivationRequested -= x).Subscribe(_ =>
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
Activate();
|
||||
}).DisposeWith(d);
|
||||
DebugViewModel vm = ViewModel!;
|
||||
Observable.FromEventPattern(x => vm.ActivationRequested += x, x => vm.ActivationRequested -= x)
|
||||
.Subscribe(_ =>
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
Activate();
|
||||
})
|
||||
.DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -2,8 +2,7 @@
|
||||
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: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"
|
||||
x:DataType="debugger:PerformanceDebugViewModel"
|
||||
x:Class="Artemis.UI.Screens.Debugger.Performance.PerformanceDebugView">
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
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:local="clr-namespace:Artemis.UI.Screens.Device"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650"
|
||||
x:Class="Artemis.UI.Screens.Device.General.DeviceGeneralTabView"
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
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:device="clr-namespace:Artemis.UI.Screens.Device"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:inputMappings="clr-namespace:Artemis.UI.Screens.Device.InputMappings"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
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:services="clr-namespace:Artemis.WebClient.Workshop.Services;assembly=Artemis.WebClient.Workshop"
|
||||
xmlns:layoutProviders="clr-namespace:Artemis.UI.Screens.Device.Layout.LayoutProviders"
|
||||
xmlns:models="clr-namespace:Artemis.WebClient.Workshop.Models;assembly=Artemis.WebClient.Workshop"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
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:device="clr-namespace:Artemis.UI.Screens.Device"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||
xmlns:leds="clr-namespace:Artemis.UI.Screens.Device.Leds"
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
Foreground="White"
|
||||
FontSize="32"
|
||||
Margin="30"
|
||||
Text=" Welcome to Artemis, the unified RGB platform.">
|
||||
Text="Welcome to Artemis, the unified RGB platform.">
|
||||
<TextBlock.Effect>
|
||||
<DropShadowEffect Color="Black" OffsetX="2" OffsetY="2" BlurRadius="5"></DropShadowEffect>
|
||||
</TextBlock.Effect>
|
||||
|
||||
@ -7,7 +7,8 @@
|
||||
xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisitesInstallDialogView"
|
||||
x:DataType="plugins:PluginPrerequisitesInstallDialogViewModel">
|
||||
x:DataType="plugins:PluginPrerequisitesInstallDialogViewModel"
|
||||
Width="800">
|
||||
<UserControl.Styles>
|
||||
<Styles>
|
||||
<Style Selector="Border.status-border">
|
||||
@ -24,7 +25,7 @@
|
||||
</Style>
|
||||
</Styles>
|
||||
</UserControl.Styles>
|
||||
<Grid ColumnDefinitions="350,*" Width="800">
|
||||
<Grid ColumnDefinitions="350,*">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition MinHeight="200" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
@ -7,7 +7,8 @@
|
||||
xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisitesUninstallDialogView"
|
||||
x:DataType="plugins:PluginPrerequisitesUninstallDialogViewModel">
|
||||
x:DataType="plugins:PluginPrerequisitesUninstallDialogViewModel"
|
||||
Width="800">
|
||||
<UserControl.Styles>
|
||||
<Styles>
|
||||
<Style Selector="Border.status-border">
|
||||
@ -24,7 +25,7 @@
|
||||
</Style>
|
||||
</Styles>
|
||||
</UserControl.Styles>
|
||||
<Grid ColumnDefinitions="350,*" Width="800">
|
||||
<Grid ColumnDefinitions="350,*">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition MinHeight="200" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
@ -3,8 +3,6 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Plugins.Features.PluginFeatureView"
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia"
|
||||
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:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
|
||||
@ -19,14 +19,11 @@ public partial class PluginSettingsWindowView : ReactiveAppWindow<PluginSettings
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
Observable.FromEventPattern(
|
||||
x => ViewModel!.ConfigurationViewModel.CloseRequested += x,
|
||||
x => ViewModel!.ConfigurationViewModel.CloseRequested -= x
|
||||
)
|
||||
PluginSettingsWindowViewModel vm = ViewModel!;
|
||||
Observable.FromEventPattern(x => vm.ConfigurationViewModel.CloseRequested += x, x => vm.ConfigurationViewModel.CloseRequested -= x)
|
||||
.Subscribe(_ => Close())
|
||||
.DisposeWith(disposables);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,7 +4,6 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Plugins.PluginView"
|
||||
|
||||
@ -6,11 +6,11 @@ using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Exceptions;
|
||||
using Artemis.UI.Services;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.Builders;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using Material.Icons;
|
||||
@ -21,9 +21,7 @@ namespace Artemis.UI.Screens.Plugins;
|
||||
|
||||
public partial class PluginViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly INotificationService _notificationService;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly IPluginInteractionService _pluginInteractionService;
|
||||
private readonly IWindowService _windowService;
|
||||
private Window? _settingsWindow;
|
||||
[Notify] private bool _canInstallPrerequisites;
|
||||
@ -31,18 +29,11 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
||||
[Notify] private bool _enabling;
|
||||
[Notify] private Plugin _plugin;
|
||||
|
||||
public PluginViewModel(Plugin plugin,
|
||||
ReactiveCommand<Unit, Unit>? reload,
|
||||
ICoreService coreService,
|
||||
IWindowService windowService,
|
||||
INotificationService notificationService,
|
||||
IPluginManagementService pluginManagementService)
|
||||
public PluginViewModel(Plugin plugin, ReactiveCommand<Unit, Unit>? reload, IWindowService windowService, IPluginInteractionService pluginInteractionService)
|
||||
{
|
||||
_plugin = plugin;
|
||||
_coreService = coreService;
|
||||
_windowService = windowService;
|
||||
_notificationService = notificationService;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_pluginInteractionService = pluginInteractionService;
|
||||
|
||||
Platforms = new ObservableCollection<PluginPlatformViewModel>();
|
||||
if (Plugin.Info.Platforms != null)
|
||||
@ -88,7 +79,6 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
||||
public ReactiveCommand<Unit, Unit> OpenPluginDirectory { get; }
|
||||
|
||||
public ObservableCollection<PluginPlatformViewModel> Platforms { get; }
|
||||
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
|
||||
public bool IsEnabled => Plugin.IsEnabled;
|
||||
|
||||
public async Task UpdateEnabled(bool enable)
|
||||
@ -97,55 +87,15 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
||||
return;
|
||||
|
||||
if (!enable)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(() => _pluginManagementService.DisablePlugin(Plugin, true));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await ShowUpdateEnableFailure(enable, e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
await _pluginInteractionService.DisablePlugin(Plugin);
|
||||
else
|
||||
{
|
||||
Enabling = true;
|
||||
if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
|
||||
{
|
||||
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
|
||||
{
|
||||
await _pluginInteractionService.EnablePlugin(Plugin, false);
|
||||
Enabling = false;
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
}
|
||||
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
}
|
||||
|
||||
public void CheckPrerequisites()
|
||||
@ -220,43 +170,12 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
||||
|
||||
private async Task ExecuteRemoveSettings()
|
||||
{
|
||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?");
|
||||
if (!confirmed)
|
||||
return;
|
||||
|
||||
bool wasEnabled = IsEnabled;
|
||||
|
||||
if (IsEnabled)
|
||||
await UpdateEnabled(false);
|
||||
|
||||
_pluginManagementService.RemovePluginSettings(Plugin);
|
||||
|
||||
if (wasEnabled)
|
||||
await UpdateEnabled(true);
|
||||
|
||||
_notificationService.CreateNotification().WithTitle("Cleared plugin settings.").Show();
|
||||
await _pluginInteractionService.RemovePluginSettings(Plugin);
|
||||
}
|
||||
|
||||
private async Task ExecuteRemove()
|
||||
{
|
||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Remove plugin", "Are you sure you want to remove this 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();
|
||||
await _pluginInteractionService.RemovePlugin(Plugin);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
@ -299,9 +204,9 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
||||
{
|
||||
if (IsEnabled)
|
||||
return;
|
||||
|
||||
|
||||
await UpdateEnabled(true);
|
||||
|
||||
|
||||
// If enabling failed, don't offer to show the settings
|
||||
if (!IsEnabled || Plugin.ConfigurationDialog == null)
|
||||
return;
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
|
||||
xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Plugins.Prerequisites.PluginPrerequisiteActionView"
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Plugins"
|
||||
xmlns:prerequisites="clr-namespace:Artemis.UI.Screens.Plugins.Prerequisites"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Plugins.Prerequisites.PluginPrerequisiteView"
|
||||
|
||||
@ -79,11 +79,6 @@ public partial class ProfileTreeViewModel : TreeItemViewModel
|
||||
|
||||
public override bool SupportsChildren => true;
|
||||
|
||||
public void UpdateCanPaste()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Task ExecuteDuplicate()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
|
||||
@ -89,7 +89,7 @@
|
||||
Background="{DynamicResource ControlFillColorDefaultBrush}"
|
||||
IsVisible="{CompiledBinding ProfileConfiguration, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<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}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@ -27,8 +27,9 @@ public partial class VisualEditorView : ReactiveUserControl<VisualEditorViewMode
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
ViewModel!.AutoFitRequested += ViewModelOnAutoFitRequested;
|
||||
Disposable.Create(() => ViewModel.AutoFitRequested -= ViewModelOnAutoFitRequested).DisposeWith(d);
|
||||
VisualEditorViewModel vm = ViewModel!;
|
||||
vm!.AutoFitRequested += ViewModelOnAutoFitRequested;
|
||||
Disposable.Create(() => vm.AutoFitRequested -= ViewModelOnAutoFitRequested).DisposeWith(d);
|
||||
});
|
||||
|
||||
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.Shared;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.MainWindow;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||
using Artemis.WebClient.Workshop.Models;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
@ -27,14 +24,12 @@ using ReactiveUI;
|
||||
|
||||
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 IProfileService _profileService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IMainWindowService _mainWindowService;
|
||||
private readonly IWorkshopService _workshopService;
|
||||
private readonly IWindowService _windowService;
|
||||
private readonly SourceList<IToolViewModel> _tools;
|
||||
private ObservableAsPropertyHelper<ProfileEditorHistory?>? _history;
|
||||
private ObservableAsPropertyHelper<bool>? _suspendedEditing;
|
||||
@ -53,16 +48,12 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
|
||||
StatusBarViewModel statusBarViewModel,
|
||||
IEnumerable<IToolViewModel> toolViewModels,
|
||||
IMainWindowService mainWindowService,
|
||||
IInputService inputService,
|
||||
IWorkshopService workshopService,
|
||||
IWindowService windowService)
|
||||
IInputService inputService)
|
||||
{
|
||||
_profileService = profileService;
|
||||
_profileEditorService = profileEditorService;
|
||||
_settingsService = settingsService;
|
||||
_mainWindowService = mainWindowService;
|
||||
_workshopService = workshopService;
|
||||
_windowService = windowService;
|
||||
|
||||
_tools = new SourceList<IToolViewModel>();
|
||||
_tools.AddRange(toolViewModels);
|
||||
@ -75,6 +66,7 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
|
||||
Tools = tools;
|
||||
visualEditorViewModel.SetTools(_tools);
|
||||
|
||||
ParameterSource = ParameterSource.Route;
|
||||
StatusBarViewModel = statusBarViewModel;
|
||||
VisualEditorViewModel = visualEditorViewModel;
|
||||
ProfileTreeViewModel = profileTreeViewModel;
|
||||
@ -193,7 +185,7 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
|
||||
#region Overrides of RoutableScreen<object,ProfileEditorViewModelParameters>
|
||||
|
||||
/// <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);
|
||||
|
||||
@ -204,23 +196,6 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
|
||||
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);
|
||||
ProfileConfiguration = profileConfiguration;
|
||||
}
|
||||
@ -236,9 +211,4 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
|
||||
}
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
Task.Run(() =>
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -93,7 +93,7 @@ public class RootViewModel : RoutableHostScreen<RoutableScreen>, IMainWindowProv
|
||||
return;
|
||||
|
||||
// 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
|
||||
coreService.Initialize();
|
||||
|
||||
|
||||
@ -19,7 +19,8 @@ public partial class SplashView : ReactiveWindow<SplashViewModel>
|
||||
#endif
|
||||
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))
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
|
||||
namespace Artemis.UI.Screens.Root;
|
||||
@ -10,12 +11,12 @@ public partial class SplashViewModel : ViewModelBase
|
||||
{
|
||||
[Notify] private string _status;
|
||||
|
||||
public SplashViewModel(ICoreService coreService, IPluginManagementService pluginManagementService)
|
||||
public SplashViewModel(ICoreService coreService, IPluginManagementService pluginManagementService, IWorkshopService workshopService)
|
||||
{
|
||||
CoreService = coreService;
|
||||
_status = "Initializing Core";
|
||||
|
||||
pluginManagementService.CopyingBuildInPlugins += OnPluginManagementServiceOnCopyingBuildInPluginsManagement;
|
||||
workshopService.MigratingBuildInPlugins += WorkshopServiceOnMigratingBuildInPlugins;
|
||||
pluginManagementService.PluginLoading += OnPluginManagementServiceOnPluginManagementLoading;
|
||||
pluginManagementService.PluginLoaded += OnPluginManagementServiceOnPluginManagementLoaded;
|
||||
pluginManagementService.PluginEnabling += PluginManagementServiceOnPluginManagementEnabling;
|
||||
@ -25,6 +26,11 @@ public partial class SplashViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
public ICoreService CoreService { get; }
|
||||
|
||||
private void WorkshopServiceOnMigratingBuildInPlugins(object? sender, EventArgs args)
|
||||
{
|
||||
Status = "Migrating built-in plugins";
|
||||
}
|
||||
|
||||
private void OnPluginManagementServiceOnPluginManagementLoaded(object? sender, PluginEventArgs args)
|
||||
{
|
||||
@ -55,9 +61,4 @@ public partial class SplashViewModel : ViewModelBase
|
||||
{
|
||||
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: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:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:vm="clr-namespace:Artemis.UI.Screens.Settings;assembly=Artemis.UI"
|
||||
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Settings.PluginsTabView"
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
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:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:ui="clr-namespace:Artemis.UI"
|
||||
|
||||
@ -187,7 +187,7 @@ public partial class ProfileConfigurationEditViewModel : DialogViewModelBase<Pro
|
||||
if (result == null)
|
||||
return;
|
||||
|
||||
SelectedBitmapSource = BitmapExtensions.LoadAndResize(result[0], 128);
|
||||
SelectedBitmapSource = BitmapExtensions.LoadAndResize(result[0], 128, false);
|
||||
_selectedIconPath = result[0];
|
||||
}
|
||||
|
||||
|
||||
@ -65,7 +65,7 @@ public partial class SidebarCategoryViewModel : ActivatableViewModelBase
|
||||
// Navigate on selection change
|
||||
this.WhenAnyValue(vm => vm.SelectedProfileConfiguration)
|
||||
.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);
|
||||
|
||||
_router.CurrentPath.WhereNotNull().Subscribe(r => SelectedProfileConfiguration = ProfileConfigurations.FirstOrDefault(c => c.Matches(r))).DisposeWith(d);
|
||||
|
||||
@ -77,7 +77,8 @@
|
||||
VerticalAlignment="Center"
|
||||
ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}"
|
||||
Width="22"
|
||||
Height="22"
|
||||
Height="22"
|
||||
CornerRadius="4"
|
||||
Margin="0 0 5 0">
|
||||
<shared:ProfileConfigurationIcon.Transitions>
|
||||
<Transitions>
|
||||
|
||||
@ -132,6 +132,6 @@ public class SidebarProfileConfigurationViewModel : ActivatableViewModelBase
|
||||
|
||||
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="240" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Sidebar.SidebarView"
|
||||
|
||||
@ -11,16 +11,16 @@
|
||||
x:DataType="startupWizard:StartupWizardViewModel"
|
||||
Icon="/Assets/Images/Logo/application.ico"
|
||||
Title="Artemis | Startup wizard"
|
||||
Width="1000"
|
||||
Width="1050"
|
||||
Height="735"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Grid Margin="15" RowDefinitions="Auto,*,Auto" ColumnDefinitions="Auto,*">
|
||||
<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" RowDefinitions="*,*" ColumnDefinitions="Auto,*,Auto">
|
||||
<Grid Margin="15" RowDefinitions="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" />
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom">
|
||||
Artemis 2
|
||||
</TextBlock>
|
||||
|
||||
<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">
|
||||
<avalonia:MaterialIcon Kind="Web" />
|
||||
@ -32,13 +32,11 @@
|
||||
<avalonia:MaterialIcon Kind="BookOpenOutline" />
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Top"
|
||||
Classes="subtitle"
|
||||
Text="{CompiledBinding Version}" />
|
||||
|
||||
<HyperlinkButton Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Top"
|
||||
@ -46,19 +44,19 @@
|
||||
PolyForm Noncommercial License 1.0.0
|
||||
</HyperlinkButton>
|
||||
</Grid>
|
||||
|
||||
<controls:Frame Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Name="Frame" IsNavigationStackEnabled="False" CacheSize="0">
|
||||
<!-- Main Content -->
|
||||
<controls:Frame Grid.Row="1" Name="Frame" IsNavigationStackEnabled="False" CacheSize="0">
|
||||
<controls:Frame.NavigationPageFactory>
|
||||
<ui:PageFactory/>
|
||||
</controls:Frame.NavigationPageFactory>
|
||||
</controls:Frame>
|
||||
|
||||
<Button Grid.Row="2" Grid.Column="0" Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding !Screen.ShowFinish}">Skip & close</Button>
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right" Orientation="Horizontal" Spacing="5" Margin="0 15 0 0">
|
||||
<Button Command="{CompiledBinding Screen.GoBack}" IsEnabled="{CompiledBinding Screen.ShowGoBack}">Back</Button>
|
||||
<Button Command="{CompiledBinding Screen.Continue}" IsVisible="{CompiledBinding !Screen.ShowFinish}" Width="80">Continue</Button>
|
||||
<Button Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding Screen.ShowFinish}" Width="80">Finish</Button>
|
||||
</StackPanel>
|
||||
<!-- Buttons Panel -->
|
||||
<DockPanel Grid.Row="2" LastChildFill="False" IsVisible="{CompiledBinding !Screen.HideAllButtons}" HorizontalSpacing="10">
|
||||
<Button Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding !Screen.ShowFinish}" DockPanel.Dock="Left">Skip & close</Button>
|
||||
<Button Command="{CompiledBinding Screen.Continue}" IsVisible="{CompiledBinding !Screen.ShowFinish}" Width="80" DockPanel.Dock="Right">Continue</Button>
|
||||
<Button Command="{CompiledBinding SkipOrFinishWizard}" IsVisible="{CompiledBinding Screen.ShowFinish}" Width="80" DockPanel.Dock="Right">Finish</Button>
|
||||
<Button Command="{CompiledBinding Screen.GoBack}" IsEnabled="{CompiledBinding Screen.ShowGoBack}" DockPanel.Dock="Right">Back</Button>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
|
||||
</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,52 +2,51 @@
|
||||
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: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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.StartupWizard.Steps.FinishStepView"
|
||||
x:DataType="steps:FinishStepViewModel">
|
||||
<Border Classes="card" VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<TextBlock Classes="h4">All finished!</TextBlock>
|
||||
<StackPanel>
|
||||
<TextBlock Classes="h4">All finished!</TextBlock>
|
||||
<Border Classes="card" VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
You are now ready to start using Artemis, enjoy! 😁
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
To learn more about Artemis and how to use it you may find these resources useful:
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
You are now ready to start using Artemis, enjoy! 😁
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
To learn more about Artemis and how to use it you may find these resources useful:
|
||||
</TextBlock>
|
||||
<Grid ColumnDefinitions="Auto,*" Margin="0 15 0 0">
|
||||
<Grid.Styles>
|
||||
<Style Selector="TextBlock.link-name">
|
||||
<Setter Property="Margin" Value="0 7 15 6" />
|
||||
<Setter Property="FontWeight" Value="600" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" Margin="0 15 0 0">
|
||||
<Grid.Styles>
|
||||
<Style Selector="TextBlock.link-name">
|
||||
<Setter Property="Margin" Value="0 7 15 6" />
|
||||
<Setter Property="FontWeight" Value="600" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
|
||||
<StackPanel Grid.Row="0" Grid.Column="0">
|
||||
<TextBlock Classes="link-name">Artemis wiki</TextBlock>
|
||||
<TextBlock Classes="link-name">Getting started guide</TextBlock>
|
||||
<TextBlock Classes="link-name">GitHub</TextBlock>
|
||||
<TextBlock Classes="link-name">Discord</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1">
|
||||
<HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/?mtm_campaign=artemis&mtm_kwd=wizard">
|
||||
https://wiki.artemis-rgb.com/
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/introduction?mtm_campaign=artemis&mtm_kwd=wizard">
|
||||
https://wiki.artemis-rgb.com/en/guides/user/introduction
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://github.com/Artemis-RGB/Artemis">
|
||||
https://github.com/Artemis-RGB/Artemis
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://discord.gg/S3MVaC9">
|
||||
https://discord.gg/S3MVaC9
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<StackPanel Grid.Row="0" Grid.Column="0">
|
||||
<TextBlock Classes="link-name">Artemis wiki</TextBlock>
|
||||
<TextBlock Classes="link-name">Getting started guide</TextBlock>
|
||||
<TextBlock Classes="link-name">GitHub</TextBlock>
|
||||
<TextBlock Classes="link-name">Discord</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1">
|
||||
<HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/?mtm_campaign=artemis&mtm_kwd=wizard">
|
||||
https://wiki.artemis-rgb.com/
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/introduction?mtm_campaign=artemis&mtm_kwd=wizard">
|
||||
https://wiki.artemis-rgb.com/en/guides/user/introduction
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://github.com/Artemis-RGB/Artemis">
|
||||
https://github.com/Artemis-RGB/Artemis
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton NavigateUri="https://discord.gg/S3MVaC9">
|
||||
https://discord.gg/S3MVaC9
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@ -2,31 +2,29 @@
|
||||
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:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
|
||||
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.LayoutsStepView"
|
||||
x:DataType="steps:LayoutsStepViewModel">
|
||||
<Border Classes="card">
|
||||
<Grid RowDefinitions="Auto,Auto,*">
|
||||
<StackPanel Grid.Row="0">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
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.
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Margin="0 10">
|
||||
Below you can automatically search the Artemis Workshop for device layouts of your devices.
|
||||
</TextBlock>
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
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.
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Margin="0 10 0 0">
|
||||
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>
|
||||
<Border Classes="card">
|
||||
<StackPanel>
|
||||
<ScrollViewer>
|
||||
<ContentControl Content="{CompiledBinding LayoutFinderViewModel}"></ContentControl>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
|
||||
<Button Grid.Row="1"
|
||||
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>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</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>
|
||||
@ -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;
|
||||
|
||||
namespace Artemis.UI.Screens.StartupWizard.Steps;
|
||||
@ -8,10 +11,12 @@ public class LayoutsStepViewModel : WizardStepViewModel
|
||||
public LayoutsStepViewModel(LayoutFinderViewModel layoutFinderViewModel)
|
||||
{
|
||||
LayoutFinderViewModel = layoutFinderViewModel;
|
||||
|
||||
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<SurfaceStepViewModel>());
|
||||
GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen<DevicesStepViewModel>());
|
||||
}
|
||||
|
||||
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<SurfaceStepViewModel>(), LayoutFinderViewModel.SearchAll.IsExecuting.Select(isExecuting => !isExecuting));
|
||||
GoBack = ReactiveCommand.Create(() => Wizard.ChangeScreen<DefaultEntriesStepViewModel>(), LayoutFinderViewModel.SearchAll.IsExecuting.Select(isExecuting => !isExecuting));
|
||||
|
||||
LayoutFinderViewModel.WhenActivated((CompositeDisposable _) => LayoutFinderViewModel.SearchAll.Execute().Subscribe());
|
||||
}
|
||||
|
||||
public LayoutFinderViewModel LayoutFinderViewModel { get; }
|
||||
}
|
||||
@ -2,125 +2,122 @@
|
||||
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:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:behaviors="clr-namespace:Artemis.UI.Shared.Behaviors;assembly=Artemis.UI.Shared"
|
||||
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.SettingsStepView"
|
||||
x:DataType="steps:SettingsStepViewModel">
|
||||
<Border Classes="card" VerticalAlignment="Top">
|
||||
<ScrollViewer>
|
||||
<StackPanel>
|
||||
<TextBlock>
|
||||
Artemis comes with a variety of settings you can change to tweak everything to your liking.
|
||||
<ScrollViewer>
|
||||
<StackPanel>
|
||||
<TextBlock>
|
||||
Artemis comes with a variety of settings you can change to tweak everything to your liking.
|
||||
</TextBlock>
|
||||
<TextBlock>
|
||||
Below you can find a few relevant settings, many more can be changed later on the settings page.
|
||||
</TextBlock>
|
||||
|
||||
<!-- Auto-run settings -->
|
||||
<StackPanel IsVisible="{CompiledBinding IsAutoRunSupported}">
|
||||
<TextBlock Classes="card-title">
|
||||
Auto-run
|
||||
</TextBlock>
|
||||
<TextBlock>
|
||||
Below you can find a few relevant settings, many more can be changed later on the settings page.
|
||||
</TextBlock>
|
||||
|
||||
<!-- Auto-run settings -->
|
||||
<StackPanel IsVisible="{CompiledBinding IsAutoRunSupported}">
|
||||
<TextBlock Classes="card-title">
|
||||
Auto-run
|
||||
</TextBlock>
|
||||
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
|
||||
<StackPanel>
|
||||
<StackPanel IsVisible="{CompiledBinding IsAutoRunSupported}">
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock>Auto-run on startup</TextBlock>
|
||||
</StackPanel>
|
||||
<ToggleSwitch Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsChecked="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10" />
|
||||
</Grid>
|
||||
<Border Classes="card-separator" />
|
||||
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock>Hide window on auto-run</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
|
||||
<ToggleSwitch IsChecked="{CompiledBinding !UIShowOnStartup.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Border Classes="card-separator" />
|
||||
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock>Associate with Artemis links</TextBlock>
|
||||
<TextBlock Classes="subtitle" TextWrapping="Wrap">
|
||||
Open Artemis when navigating to artemis:// links, allows opening workshop entries from your browser.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
|
||||
<ToggleSwitch IsChecked="{CompiledBinding UIUseProtocol.Value}" OnContent="Yes" OffContent="No" MinWidth="0" Margin="0 -10" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Border Classes="card-separator" />
|
||||
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock>Startup delay</TextBlock>
|
||||
<TextBlock Classes="subtitle" TextWrapping="Wrap">
|
||||
Set the amount of seconds to wait before auto-running Artemis.
|
||||
</TextBlock>
|
||||
<TextBlock Classes="subtitle" TextWrapping="Wrap">
|
||||
If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<controls:NumberBox IsEnabled="{CompiledBinding UIAutoRun.Value}" Width="120">
|
||||
<Interaction.Behaviors>
|
||||
<behaviors:LostFocusNumberBoxBindingBehavior Value="{CompiledBinding UIAutoRunDelay.Value}" />
|
||||
</Interaction.Behaviors>
|
||||
</controls:NumberBox>
|
||||
<TextBlock VerticalAlignment="Center" TextAlignment="Right" Width="30">sec</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Update settings -->
|
||||
<StackPanel>
|
||||
<TextBlock Classes="card-title">
|
||||
Updating
|
||||
</TextBlock>
|
||||
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
|
||||
<StackPanel>
|
||||
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
|
||||
<StackPanel>
|
||||
<StackPanel IsVisible="{CompiledBinding IsAutoRunSupported}">
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock>
|
||||
Check for updates
|
||||
</TextBlock>
|
||||
<TextBlock Classes="subtitle" TextWrapping="Wrap">
|
||||
If enabled, we'll check for updates on startup and periodically while running.
|
||||
</TextBlock>
|
||||
<TextBlock>Auto-run on startup</TextBlock>
|
||||
</StackPanel>
|
||||
<ToggleSwitch Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsChecked="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10" />
|
||||
</Grid>
|
||||
<Border Classes="card-separator" />
|
||||
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock>Hide window on auto-run</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
|
||||
<ToggleSwitch IsChecked="{CompiledBinding UICheckForUpdates.Value}" MinWidth="0" />
|
||||
<ToggleSwitch IsChecked="{CompiledBinding !UIShowOnStartup.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Border Classes="card-separator" />
|
||||
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock>
|
||||
Auto-install updates
|
||||
</TextBlock>
|
||||
<TextBlock>Associate with Artemis links</TextBlock>
|
||||
<TextBlock Classes="subtitle" TextWrapping="Wrap">
|
||||
If enabled, new updates will automatically be installed.
|
||||
Open Artemis when navigating to artemis:// links, allows opening workshop entries from your browser.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
|
||||
<ToggleSwitch IsEnabled="{CompiledBinding UICheckForUpdates.Value}" IsChecked="{CompiledBinding UIAutoUpdate.Value}" MinWidth="0" />
|
||||
<ToggleSwitch IsChecked="{CompiledBinding UIUseProtocol.Value}" OnContent="Yes" OffContent="No" MinWidth="0" Margin="0 -10" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Border Classes="card-separator" />
|
||||
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock>Startup delay</TextBlock>
|
||||
<TextBlock Classes="subtitle" TextWrapping="Wrap">
|
||||
Set the amount of seconds to wait before auto-running Artemis.
|
||||
</TextBlock>
|
||||
<TextBlock Classes="subtitle" TextWrapping="Wrap">
|
||||
If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<controls:NumberBox IsEnabled="{CompiledBinding UIAutoRun.Value}" Width="120">
|
||||
<Interaction.Behaviors>
|
||||
<behaviors:LostFocusNumberBoxBindingBehavior Value="{CompiledBinding UIAutoRunDelay.Value}" />
|
||||
</Interaction.Behaviors>
|
||||
</controls:NumberBox>
|
||||
<TextBlock VerticalAlignment="Center" TextAlignment="Right" Width="30">sec</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<!-- Update settings -->
|
||||
<StackPanel>
|
||||
<TextBlock Classes="card-title">
|
||||
Updating
|
||||
</TextBlock>
|
||||
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
|
||||
<StackPanel>
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock>
|
||||
Check for updates
|
||||
</TextBlock>
|
||||
<TextBlock Classes="subtitle" TextWrapping="Wrap">
|
||||
If enabled, we'll check for updates on startup and periodically while running.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
|
||||
<ToggleSwitch IsChecked="{CompiledBinding UICheckForUpdates.Value}" MinWidth="0" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Border Classes="card-separator" />
|
||||
|
||||
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock>
|
||||
Auto-install updates
|
||||
</TextBlock>
|
||||
<TextBlock Classes="subtitle" TextWrapping="Wrap">
|
||||
If enabled, new updates will automatically be installed.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
|
||||
<ToggleSwitch IsEnabled="{CompiledBinding UICheckForUpdates.Value}" IsChecked="{CompiledBinding UIAutoUpdate.Value}" MinWidth="0" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@ -37,8 +37,9 @@ public class SettingsStepViewModel : WizardStepViewModel
|
||||
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<FinishStepViewModel>());
|
||||
GoBack = ReactiveCommand.Create(() =>
|
||||
{
|
||||
// Without devices, skip to the default entries screen
|
||||
if (deviceService.EnabledDevices.Count == 0)
|
||||
Wizard.ChangeScreen<DevicesStepViewModel>();
|
||||
Wizard.ChangeScreen<DefaultEntriesStepViewModel>();
|
||||
else
|
||||
Wizard.ChangeScreen<SurfaceStepViewModel>();
|
||||
});
|
||||
|
||||
@ -2,24 +2,20 @@
|
||||
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:startupWizard="clr-namespace:Artemis.UI.Screens.StartupWizard"
|
||||
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.SurfaceStepView"
|
||||
x:DataType="steps:SurfaceStepViewModel">
|
||||
|
||||
<Grid RowDefinitions="Auto,*,Auto,Auto" ColumnDefinitions="*,*">
|
||||
<Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Classes="card">
|
||||
<StackPanel>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Artemis uses "spatial awareness" to create realistic effects across multiple devices.
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
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>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Spacing="10">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Artemis uses "spatial awareness" to create realistic effects across multiple devices.
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
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>
|
||||
</StackPanel>
|
||||
|
||||
<Button Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
@ -28,15 +24,14 @@
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 0 10 0"
|
||||
Width="280"
|
||||
Height="280"
|
||||
IsEnabled="False">
|
||||
Height="280">
|
||||
<StackPanel>
|
||||
<avalonia:MaterialIcon Kind="HandBackLeft" Width="150" Height="150" HorizontalAlignment="Center" />
|
||||
<TextBlock TextAlignment="Center" Classes="h4" Margin="0 10 0 0">
|
||||
Left-handed preset (NYI)
|
||||
Left-handed preset
|
||||
</TextBlock>
|
||||
<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>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
@ -27,9 +27,7 @@ public class SurfaceStepViewModel : WizardStepViewModel
|
||||
|
||||
private void ExecuteSelectLayout(string layout)
|
||||
{
|
||||
// TODO: Implement the layout
|
||||
_deviceService.AutoArrangeDevices();
|
||||
|
||||
_deviceService.AutoArrangeDevices(layout == "left");
|
||||
Wizard.ChangeScreen<SettingsStepViewModel>();
|
||||
}
|
||||
}
|
||||
@ -6,19 +6,17 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.StartupWizard.Steps.WelcomeStepView"
|
||||
x:DataType="steps:WelcomeStepViewModel">
|
||||
<Border Classes="card">
|
||||
<StackPanel>
|
||||
<TextBlock Classes="h4">Welcome to the Artemis startup wizard!</TextBlock>
|
||||
<StackPanel Margin="0 50 0 0">
|
||||
<TextBlock Classes="h4" TextAlignment="Center">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.
|
||||
</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.
|
||||
</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.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@ -6,7 +6,7 @@ public class WelcomeStepViewModel : WizardStepViewModel
|
||||
{
|
||||
public WelcomeStepViewModel()
|
||||
{
|
||||
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<DevicesStepViewModel>());
|
||||
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<DefaultEntriesStepViewModel>());
|
||||
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;
|
||||
|
||||
public partial class DevicesStepView : ReactiveUserControl<DevicesStepViewModel>
|
||||
public partial class WorkshopUnreachableStepView : ReactiveUserControl<WorkshopUnreachableStepViewModel>
|
||||
{
|
||||
public DevicesStepView()
|
||||
public WorkshopUnreachableStepView()
|
||||
{
|
||||
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:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.StartupWizard.WizardPluginFeatureView"
|
||||
|
||||
@ -15,6 +15,7 @@ public abstract partial class WizardStepViewModel : ValidatableViewModelBase
|
||||
[Notify] private bool _showFinish;
|
||||
[Notify] private bool _showGoBack = true;
|
||||
[Notify] private bool _showHeader = true;
|
||||
[Notify] private bool _hideAllButtons;
|
||||
|
||||
public StartupWizardViewModel Wizard { get; set; } = null!;
|
||||
}
|
||||
@ -13,6 +13,7 @@ using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Avalonia;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
using SkiaSharp;
|
||||
@ -180,11 +181,18 @@ public partial class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewMod
|
||||
|
||||
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.");
|
||||
if (!confirmed)
|
||||
return;
|
||||
|
||||
_deviceService.AutoArrangeDevices();
|
||||
ContentDialogResult contentDialogResult = await _windowService.CreateContentDialog()
|
||||
.WithTitle("Auto-arrange layout")
|
||||
.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();
|
||||
|
||||
if (contentDialogResult == ContentDialogResult.Primary)
|
||||
_deviceService.AutoArrangeDevices(true);
|
||||
else if (contentDialogResult == ContentDialogResult.Secondary)
|
||||
_deviceService.AutoArrangeDevices(false);
|
||||
}
|
||||
|
||||
private void RenderServiceOnFrameRendering(object? sender, FrameRenderingEventArgs e)
|
||||
|
||||
@ -33,16 +33,17 @@ public partial class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
ViewModel!.AutoFitRequested += ViewModelOnAutoFitRequested;
|
||||
ViewModel.PickerPositionSubject.Subscribe(ShowPickerAt).DisposeWith(d);
|
||||
if (ViewModel.IsPreview)
|
||||
NodeScriptViewModel vm = ViewModel!;
|
||||
vm.AutoFitRequested += ViewModelOnAutoFitRequested;
|
||||
vm.PickerPositionSubject.Subscribe(ShowPickerAt).DisposeWith(d);
|
||||
if (vm.IsPreview)
|
||||
{
|
||||
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);
|
||||
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
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:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
||||
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"
|
||||
x:Class="Artemis.UI.Screens.VisualScripting.NodeScriptWindowView"
|
||||
x:DataType="visualScripting:NodeScriptWindowViewModel"
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
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"
|
||||
x:Class="Artemis.UI.Screens.VisualScripting.NodeView"
|
||||
x:DataType="visualScripting:NodeViewModel">
|
||||
|
||||
@ -18,10 +18,10 @@ public class CategoriesViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private ObservableAsPropertyHelper<IReadOnlyList<EntryFilterInput>?>? _categoryFilters;
|
||||
|
||||
public CategoriesViewModel(IWorkshopClient client)
|
||||
public CategoriesViewModel(EntryType entryType, IWorkshopClient client)
|
||||
{
|
||||
client.GetCategories
|
||||
.Watch(ExecutionStrategy.CacheFirst)
|
||||
.Watch(entryType, ExecutionStrategy.CacheFirst)
|
||||
.SelectOperationResult(c => c.Categories)
|
||||
.ToObservableChangeSet(c => c.Id)
|
||||
.Transform(c => new CategoryViewModel(c))
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:currentUser="clr-namespace:Artemis.UI.Screens.Workshop.CurrentUser"
|
||||
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"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.CurrentUser.CurrentUserView"
|
||||
|
||||
@ -51,11 +51,11 @@
|
||||
ToolTip.Tip="Click to browse">
|
||||
</Button>
|
||||
</Panel>
|
||||
|
||||
</Border>
|
||||
<TextBlock Foreground="{DynamicResource SystemFillColorCriticalBrush}" Margin="2 0" IsVisible="{CompiledBinding !IconValid}" TextWrapping="Wrap">
|
||||
Icon required
|
||||
</TextBlock>
|
||||
<CheckBox IsChecked="{CompiledBinding Fit}">Shrink</CheckBox>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1">
|
||||
<Label Target="Name" Margin="0">Name</Label>
|
||||
@ -63,8 +63,12 @@
|
||||
|
||||
<Label Target="Summary" Margin="0 5 0 0">Summary</Label>
|
||||
<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>
|
||||
</Grid>
|
||||
|
||||
@ -93,10 +97,10 @@
|
||||
<Label>Tags</Label>
|
||||
<tagsInput:TagsInput Tags="{CompiledBinding Tags}" />
|
||||
</StackPanel>
|
||||
|
||||
<controls:SplitMarkdownEditor Grid.Row="1" Title="Description" Markdown="{CompiledBinding Description}"/>
|
||||
|
||||
<TextBlock Grid.Row="2"
|
||||
|
||||
<controls:SplitMarkdownEditor Grid.Row="1" Title="Description" Markdown="{CompiledBinding Description}" />
|
||||
|
||||
<TextBlock Grid.Row="2"
|
||||
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
|
||||
Margin="2 8 0 0"
|
||||
IsVisible="{CompiledBinding !DescriptionValid}">
|
||||
|
||||
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