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

Compare commits

..

2 Commits

Author SHA1 Message Date
Diogo Trindade
afa4c508b3
Remove Flurl 2023-11-05 18:15:37 +00:00
Diogo Trindade
c67d2ab6fe
Depend on each package only once
I dont know if this is the 'good-practice' way to do this, but it seems much less error prone to me to depend on each package in only one project. dotnet should handle implicit references.

This commit references (almost) each package in the lowest level it's needed, and assumes implicit references on above levels.
2023-11-05 18:15:05 +00:00
145 changed files with 1140 additions and 2250 deletions

View File

@ -1,4 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Artemis.props" />
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<PreserveCompilationContext>false</PreserveCompilationContext>
@ -36,20 +38,21 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
<PackageReference Include="DryIoc.dll" Version="5.4.2" />
<PackageReference Include="EmbedIO" Version="3.5.2" />
<PackageReference Include="HidSharp" Version="2.1.0" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="JetBrains.Annotations" Version="2023.2.0" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="RGB.NET.Core" Version="2.0.4-prerelease.1" />
<PackageReference Include="RGB.NET.Layout" Version="2.0.4-prerelease.1" />
<PackageReference Include="RGB.NET.Presets" Version="2.0.4-prerelease.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="RGB.NET.Core" Version="$(RGBDotNetVersion)" />
<PackageReference Include="RGB.NET.Presets" Version="$(RGBDotNetVersion)" />
<PackageReference Include="RGB.NET.Layout" Version="$(RGBDotNetVersion)" />
<PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="SkiaSharp" Version="2.88.7" />
<PackageReference Include="SkiaSharp" Version="$(SkiaSharpVersion)" />
</ItemGroup>
<ItemGroup>

View File

@ -47,7 +47,6 @@ public static class Constants
/// The full path to the Artemis logs folder
/// </summary>
public static readonly string LogsFolder = Path.Combine(DataFolder, "Logs");
/// <summary>
/// The full path to the Artemis logs folder
/// </summary>
@ -62,11 +61,6 @@ public static class Constants
/// The full path to the Artemis user layouts folder
/// </summary>
public static readonly string LayoutsFolder = Path.Combine(DataFolder, "User Layouts");
/// <summary>
/// The full path to the Artemis user layouts folder
/// </summary>
public static readonly string WorkshopFolder = Path.Combine(DataFolder, "workshop");
/// <summary>
/// The current API version for plugins
@ -77,9 +71,9 @@ public static class Constants
/// <summary>
/// The current version of the application
/// </summary>
public static readonly string CurrentVersion = CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion.StartsWith("1.0.0")
? "local"
: CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion;
public static readonly string CurrentVersion = CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion != "1.0.0"
? CoreAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!.InformationalVersion
: "local";
/// <summary>
/// The plugin info used by core components of Artemis
@ -160,5 +154,4 @@ public static class Constants
/// Gets the graphics context to be used for rendering by SkiaSharp.
/// </summary>
public static IManagedGraphicsContext? ManagedGraphicsContext { get; internal set; }
}

View File

@ -2,7 +2,6 @@ using System;
using System.Linq;
using System.Reflection;
using Artemis.Core.DryIoc.Factories;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.Storage;
using Artemis.Storage.Migrations.Interfaces;
@ -36,8 +35,7 @@ public static class ContainerExtensions
// Bind migrations
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IStorageMigration>(), Reuse.Singleton, nonPublicServiceTypes: true);
container.RegisterMany(coreAssembly, type => type.IsAssignableTo<ILayoutProvider>(), Reuse.Singleton);
container.Register<IPluginSettingsFactory, PluginSettingsFactory>(Reuse.Singleton);
container.Register(Made.Of(_ => ServiceInfo.Of<IPluginSettingsFactory>(), f => f.CreatePluginSettings(Arg.Index<Type>(0)), r => r.Parent.ImplementationType));
container.Register<ILoggerFactory, LoggerFactory>(Reuse.Singleton);

View File

@ -0,0 +1,31 @@
using System.IO;
namespace Artemis.Core;
internal static class DirectoryInfoExtensions
{
public static void CopyFilesRecursively(this DirectoryInfo source, DirectoryInfo target)
{
foreach (DirectoryInfo dir in source.GetDirectories())
CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
foreach (FileInfo file in source.GetFiles())
file.CopyTo(Path.Combine(target.FullName, file.Name));
}
public static void DeleteRecursively(this DirectoryInfo baseDir)
{
if (!baseDir.Exists)
return;
foreach (DirectoryInfo dir in baseDir.EnumerateDirectories())
DeleteRecursively(dir);
FileInfo[] files = baseDir.GetFiles();
foreach (FileInfo file in files)
{
file.IsReadOnly = false;
file.Delete();
}
baseDir.Delete();
}
}

View File

@ -2,9 +2,9 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using Artemis.Core.DeviceProviders;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Surface;
using RGB.NET.Core;
@ -46,7 +46,6 @@ public class ArtemisDevice : CorePropertyChanged
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
Categories = new HashSet<DeviceCategory>();
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LayoutType};
RgbDevice.ColorCorrections.Clear();
RgbDevice.ColorCorrections.Add(new ScaleColorCorrection(this));
@ -75,7 +74,6 @@ public class ArtemisDevice : CorePropertyChanged
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
Categories = new HashSet<DeviceCategory>();
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LayoutType};
foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers)
InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier));
@ -155,8 +153,6 @@ public class ArtemisDevice : CorePropertyChanged
/// </summary>
public HashSet<DeviceCategory> Categories { get; }
public LayoutSelection LayoutSelection { get; }
/// <summary>
/// Gets or sets the X-position of the device
/// </summary>
@ -297,6 +293,19 @@ public class ArtemisDevice : CorePropertyChanged
}
}
/// <summary>
/// Gets or sets a boolean indicating whether falling back to default layouts is enabled or not
/// </summary>
public bool DisableDefaultLayout
{
get => DeviceEntity.DisableDefaultLayout;
set
{
DeviceEntity.DisableDefaultLayout = value;
OnPropertyChanged(nameof(DisableDefaultLayout));
}
}
/// <summary>
/// Gets or sets the logical layout of the device e.g. DE, UK or US.
/// <para>Only applicable to keyboards</para>
@ -311,6 +320,20 @@ public class ArtemisDevice : CorePropertyChanged
}
}
/// <summary>
/// Gets or sets the path of the custom layout to load when calling <see cref="GetBestDeviceLayout" />
/// for this device
/// </summary>
public string? CustomLayoutPath
{
get => DeviceEntity.CustomLayoutPath;
set
{
DeviceEntity.CustomLayoutPath = value;
OnPropertyChanged(nameof(CustomLayoutPath));
}
}
/// <summary>
/// Gets the layout of the device expanded with Artemis-specific data
/// </summary>
@ -358,6 +381,40 @@ public class ArtemisDevice : CorePropertyChanged
return artemisLed;
}
/// <summary>
/// Returns the most preferred device layout for this device.
/// </summary>
/// <returns>The most preferred device layout for this device.</returns>
public ArtemisLayout? GetBestDeviceLayout()
{
ArtemisLayout? layout;
// Configured layout path takes precedence over all other options
if (CustomLayoutPath != null)
{
layout = new ArtemisLayout(CustomLayoutPath, LayoutSource.Configured);
if (layout.IsValid)
return layout;
}
// Look for a layout provided by the user
layout = DeviceProvider.LoadUserLayout(this);
if (layout.IsValid)
return layout;
if (DisableDefaultLayout)
return null;
// Look for a layout provided by the plugin
layout = DeviceProvider.LoadLayout(this);
if (layout.IsValid)
return layout;
// Finally fall back to a default layout
layout = ArtemisLayout.GetDefaultLayout(this);
return layout;
}
/// <summary>
/// Occurs when the underlying RGB.NET device was updated
/// </summary>
@ -398,6 +455,14 @@ public class ArtemisDevice : CorePropertyChanged
}
}
/// <summary>
/// Invokes the <see cref="DeviceUpdated" /> event
/// </summary>
protected virtual void OnDeviceUpdated()
{
DeviceUpdated?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Applies the provided layout to the device
/// </summary>
@ -410,13 +475,13 @@ public class ArtemisDevice : CorePropertyChanged
/// A boolean indicating whether to remove excess LEDs present in the device but missing
/// in the layout
/// </param>
public void ApplyLayout(ArtemisLayout? layout, bool createMissingLeds, bool removeExcessiveLeds)
internal void ApplyLayout(ArtemisLayout? layout, bool createMissingLeds, bool removeExcessiveLeds)
{
if (layout != null && layout.IsValid && createMissingLeds && !DeviceProvider.CreateMissingLedsSupported)
throw new ArtemisCoreException($"Cannot apply layout with {nameof(createMissingLeds)} set to true because the device provider does not support it");
if (layout != null && layout.IsValid && removeExcessiveLeds && !DeviceProvider.RemoveExcessiveLedsSupported)
throw new ArtemisCoreException($"Cannot apply layout with {nameof(removeExcessiveLeds)} set to true because the device provider does not support it");
// Always clear the current layout
ClearLayout();
@ -437,14 +502,6 @@ public class ArtemisDevice : CorePropertyChanged
CalculateRenderProperties();
}
/// <summary>
/// Invokes the <see cref="DeviceUpdated" /> event
/// </summary>
protected virtual void OnDeviceUpdated()
{
DeviceUpdated?.Invoke(this, EventArgs.Empty);
}
private void ClearLayout()
{
if (Layout == null)
@ -477,9 +534,6 @@ public class ArtemisDevice : CorePropertyChanged
DeviceEntity.Categories.Clear();
foreach (DeviceCategory deviceCategory in Categories)
DeviceEntity.Categories.Add((int) deviceCategory);
DeviceEntity.LayoutType = LayoutSelection.Type;
DeviceEntity.LayoutParameter = LayoutSelection.Parameter;
}
internal void Load()
@ -494,9 +548,6 @@ public class ArtemisDevice : CorePropertyChanged
if (!Categories.Any())
ApplyDefaultCategories();
LayoutSelection.Type = DeviceEntity.LayoutType;
LayoutSelection.Parameter = DeviceEntity.LayoutParameter;
LoadInputMappings();
}
@ -522,7 +573,7 @@ public class ArtemisDevice : CorePropertyChanged
{
Leds = RgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly();
LedIds = new ReadOnlyDictionary<LedId, ArtemisLed>(Leds.ToDictionary(l => l.RgbLed.Id, l => l));
if (loadInputMappings)
LoadInputMappings();
}

View File

@ -12,18 +12,17 @@ namespace Artemis.Core;
/// </summary>
public class ArtemisLayout
{
private static readonly string DefaultLayoutPath = Path.Combine(Constants.ApplicationFolder, "DefaultLayouts", "Artemis");
/// <summary>
/// Creates a new instance of the <see cref="ArtemisLayout" /> class
/// </summary>
/// <param name="filePath">The path of the layout XML file</param>
public ArtemisLayout(string filePath)
/// <param name="source">The source from where this layout is being loaded</param>
public ArtemisLayout(string filePath, LayoutSource source)
{
FilePath = filePath;
Source = source;
Leds = new List<ArtemisLedLayout>();
IsDefaultLayout = filePath.StartsWith(DefaultLayoutPath);
LoadLayout();
}
@ -32,6 +31,11 @@ public class ArtemisLayout
/// </summary>
public string FilePath { get; }
/// <summary>
/// Gets the source from where this layout was loaded
/// </summary>
public LayoutSource Source { get; }
/// <summary>
/// Gets a boolean indicating whether a valid layout was loaded
/// </summary>
@ -57,8 +61,6 @@ public class ArtemisLayout
/// </summary>
public LayoutCustomDeviceData LayoutCustomDeviceData { get; private set; } = null!;
public bool IsDefaultLayout { get; private set; }
/// <summary>
/// Applies the layout to the provided device
/// </summary>
@ -119,28 +121,29 @@ public class ArtemisLayout
internal static ArtemisLayout? GetDefaultLayout(ArtemisDevice device)
{
string layoutFolder = Path.Combine(Constants.ApplicationFolder, "DefaultLayouts", "Artemis");
if (device.DeviceType == RGBDeviceType.Keyboard)
{
// XL layout is defined by its programmable macro keys
if (device.Leds.Any(l => l.RgbLed.Id >= LedId.Keyboard_Programmable1 && l.RgbLed.Id <= LedId.Keyboard_Programmable32))
{
if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis XL keyboard-ANSI.xml"));
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis XL keyboard-ISO.xml"));
return new ArtemisLayout(Path.Combine(layoutFolder, "Keyboard", "Artemis XL keyboard-ANSI.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder, "Keyboard", "Artemis XL keyboard-ISO.xml"), LayoutSource.Default);
}
// L layout is defined by its numpad
if (device.Leds.Any(l => l.RgbLed.Id >= LedId.Keyboard_NumLock && l.RgbLed.Id <= LedId.Keyboard_NumPeriodAndDelete))
{
if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis L keyboard-ANSI.xml"));
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis L keyboard-ISO.xml"));
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis L keyboard-ANSI.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis L keyboard-ISO.xml"), LayoutSource.Default);
}
// No numpad will result in TKL
if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis TKL keyboard-ANSI.xml"));
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis TKL keyboard-ISO.xml"));
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis TKL keyboard-ANSI.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis TKL keyboard-ISO.xml"), LayoutSource.Default);
}
// if (device.DeviceType == RGBDeviceType.Mouse)
@ -148,21 +151,21 @@ public class ArtemisLayout
// if (device.Leds.Count == 1)
// {
// if (device.Leds.Any(l => l.RgbLed.Id == LedId.Logo))
// return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "1 LED mouse logo.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "1 LED mouse.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "1 LED mouse logo.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "1 LED mouse.xml"), LayoutSource.Default);
// }
// if (device.Leds.Any(l => l.RgbLed.Id == LedId.Logo))
// return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "4 LED mouse logo.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "4 LED mouse.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "4 LED mouse logo.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "4 LED mouse.xml"), LayoutSource.Default);
// }
if (device.DeviceType == RGBDeviceType.Headset)
{
if (device.Leds.Count == 1)
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Headset", "Artemis 1 LED headset.xml"));
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 1 LED headset.xml"), LayoutSource.Default);
if (device.Leds.Count == 2)
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Headset", "Artemis 2 LED headset.xml"));
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Headset", "Artemis 4 LED headset.xml"));
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 2 LED headset.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 4 LED headset.xml"), LayoutSource.Default);
}
return null;
@ -203,10 +206,30 @@ public class ArtemisLayout
else
Image = null;
}
}
/// <inheritdoc />
public override string ToString()
{
return FilePath;
}
/// <summary>
/// Represents a source from where a layout came
/// </summary>
public enum LayoutSource
{
/// <summary>
/// A layout loaded from config
/// </summary>
Configured,
/// <summary>
/// A layout loaded from the user layout folder
/// </summary>
User,
/// <summary>
/// A layout loaded from the plugin folder
/// </summary>
Plugin,
/// <summary>
/// A default layout loaded as a fallback option
/// </summary>
Default
}

View File

@ -1,28 +0,0 @@
namespace Artemis.Core;
/// <summary>
/// Represents a reference to a layout for a device.
/// </summary>
public class LayoutSelection : CorePropertyChanged
{
private string? _type;
private string? _parameter;
/// <summary>
/// Gets or sets what kind of layout reference this is.
/// </summary>
public string? Type
{
get => _type;
set => SetAndNotify(ref _type, value);
}
/// <summary>
/// Gets or sets the parameter of the layout reference, such as a file path of workshop entry ID.
/// </summary>
public string? Parameter
{
get => _parameter;
set => SetAndNotify(ref _parameter, value);
}
}

View File

@ -62,7 +62,7 @@ public abstract class DeviceProvider : PluginFeature
device.DeviceType.ToString(),
GetDeviceLayoutName(device)
);
return new ArtemisLayout(filePath);
return new ArtemisLayout(filePath, LayoutSource.Plugin);
}
/// <summary>
@ -79,7 +79,7 @@ public abstract class DeviceProvider : PluginFeature
device.DeviceType.ToString(),
GetDeviceLayoutName(device)
);
return new ArtemisLayout(filePath);
return new ArtemisLayout(filePath, LayoutSource.User);
}
/// <summary>

View File

@ -1,37 +0,0 @@
namespace Artemis.Core.Providers;
public class CustomPathLayoutProvider : ILayoutProvider
{
public static string LayoutType = "CustomPath";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
{
if (device.LayoutSelection.Parameter == null)
return null;
return new ArtemisLayout(device.LayoutSelection.Parameter);
}
/// <inheritdoc />
public void ApplyLayout(ArtemisDevice device, ArtemisLayout layout)
{
device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported);
}
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LayoutType;
}
/// <summary>
/// Configures the provided device to use this layout provider.
/// </summary>
/// <param name="device">The device to apply the provider to.</param>
/// <param name="path">The path to the custom layout.</param>
public void ConfigureDevice(ArtemisDevice device, string? path)
{
device.LayoutSelection.Type = LayoutType;
device.LayoutSelection.Parameter = path;
}
}

View File

@ -1,41 +0,0 @@
namespace Artemis.Core.Providers;
public class DefaultLayoutProvider : ILayoutProvider
{
public static string LayoutType = "Default";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
{
// Look for a layout provided by the plugin
ArtemisLayout layout = device.DeviceProvider.LoadLayout(device);
// Finally fall back to a default layout
return layout.IsValid ? layout : ArtemisLayout.GetDefaultLayout(device);
}
/// <inheritdoc />
public void ApplyLayout(ArtemisDevice device, ArtemisLayout layout)
{
if (layout.IsDefaultLayout)
device.ApplyLayout(layout, false, false);
else
device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported);
}
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LayoutType;
}
/// <summary>
/// Configures the provided device to use this layout provider.
/// </summary>
/// <param name="device">The device to apply the provider to.</param>
public void ConfigureDevice(ArtemisDevice device)
{
device.LayoutSelection.Type = LayoutType;
device.LayoutSelection.Parameter = null;
}
}

View File

@ -1,17 +0,0 @@
namespace Artemis.Core.Providers;
/// <summary>
/// Represents a class that can provide Artemis layouts for devices.
/// </summary>
public interface ILayoutProvider
{
/// <summary>
/// If available, loads an Artemis layout for the provided device.
/// </summary>
/// <param name="device">The device to load the layout for.</param>
/// <returns>The resulting layout if one was available; otherwise <see langword="null" />.</returns>
ArtemisLayout? GetDeviceLayout(ArtemisDevice device);
void ApplyLayout(ArtemisDevice device, ArtemisLayout layout);
bool IsMatch(ArtemisDevice device);
}

View File

@ -1,34 +0,0 @@
namespace Artemis.Core.Providers;
public class NoneLayoutProvider : ILayoutProvider
{
public static string LayoutType = "None";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
{
return null;
}
/// <inheritdoc />
public void ApplyLayout(ArtemisDevice device, ArtemisLayout layout)
{
device.ApplyLayout(null, false, false);
}
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LayoutType;
}
/// <summary>
/// Configures the provided device to use this layout provider.
/// </summary>
/// <param name="device">The device to apply the provider to.</param>
public void ConfigureDevice(ArtemisDevice device)
{
device.LayoutSelection.Type = LayoutType;
device.LayoutSelection.Parameter = null;
}
}

View File

@ -4,11 +4,9 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core.DeviceProviders;
using Artemis.Core.Providers;
using Artemis.Core.Services.Models;
using Artemis.Storage.Entities.Surface;
using Artemis.Storage.Repositories.Interfaces;
using DryIoc;
using RGB.NET.Core;
using Serilog;
@ -20,21 +18,15 @@ internal class DeviceService : IDeviceService
private readonly IPluginManagementService _pluginManagementService;
private readonly IDeviceRepository _deviceRepository;
private readonly Lazy<IRenderService> _renderService;
private readonly Func<List<ILayoutProvider>> _getLayoutProviders;
private readonly List<ArtemisDevice> _enabledDevices = new();
private readonly List<ArtemisDevice> _devices = new();
public DeviceService(ILogger logger,
IPluginManagementService pluginManagementService,
IDeviceRepository deviceRepository,
Lazy<IRenderService> renderService,
Func<List<ILayoutProvider>> getLayoutProviders)
public DeviceService(ILogger logger, IPluginManagementService pluginManagementService, IDeviceRepository deviceRepository, Lazy<IRenderService> renderService)
{
_logger = logger;
_pluginManagementService = pluginManagementService;
_deviceRepository = deviceRepository;
_renderService = renderService;
_getLayoutProviders = getLayoutProviders;
EnabledDevices = new ReadOnlyCollection<ArtemisDevice>(_enabledDevices);
Devices = new ReadOnlyCollection<ArtemisDevice>(_devices);
@ -165,30 +157,12 @@ internal class DeviceService : IDeviceService
}
/// <inheritdoc />
public void LoadDeviceLayout(ArtemisDevice device)
public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout)
{
ILayoutProvider? provider = _getLayoutProviders().FirstOrDefault(p => p.IsMatch(device));
if (provider == null)
_logger.Warning("Could not find a layout provider for type {LayoutType} of device {Device}", device.LayoutSelection.Type, device);
ArtemisLayout? layout = provider?.GetDeviceLayout(device);
if (layout != null && !layout.IsValid)
{
_logger.Warning("Got an invalid layout {Layout} from {LayoutProvider}", layout, provider!.GetType().FullName);
layout = null;
}
try
{
if (layout == null)
device.ApplyLayout(null, false, false);
else
provider?.ApplyLayout(device, layout);
}
catch (Exception e)
{
_logger.Error(e, "Failed to apply device layout");
}
if (layout == null || layout.Source == LayoutSource.Default)
device.ApplyLayout(layout, false, false);
else
device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported);
UpdateLeds();
}
@ -256,7 +230,7 @@ internal class DeviceService : IDeviceService
device = new ArtemisDevice(rgbDevice, deviceProvider);
}
LoadDeviceLayout(device);
ApplyDeviceLayout(device, device.GetBestDeviceLayout());
return device;
}

View File

@ -43,10 +43,11 @@ public interface IDeviceService : IArtemisService
void AutoArrangeDevices();
/// <summary>
/// Apples the best available to the provided <see cref="ArtemisDevice" />
/// Apples the provided <see cref="ArtemisLayout" /> to the provided <see cref="ArtemisDevice" />
/// </summary>
/// <param name="device"></param>
void LoadDeviceLayout(ArtemisDevice device);
/// <param name="layout"></param>
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout);
/// <summary>
/// Enables the provided device

View File

@ -57,7 +57,7 @@ internal class PluginManagementService : IPluginManagementService
// Remove the old directory if it exists
if (Directory.Exists(pluginDirectory.FullName))
pluginDirectory.Delete(true);
pluginDirectory.DeleteRecursively();
// Extract everything in the same archive directory to the unique plugin directory
Utilities.CreateAccessibleDirectory(pluginDirectory.FullName);

View File

@ -1,4 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Artemis.props" />
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<PreserveCompilationContext>false</PreserveCompilationContext>
@ -7,6 +9,6 @@
<ItemGroup>
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog" Version="3.0.1" />
</ItemGroup>
</Project>

View File

@ -22,11 +22,11 @@ public class DeviceEntity
public float GreenScale { get; set; }
public float BlueScale { get; set; }
public bool IsEnabled { get; set; }
public bool DisableDefaultLayout { get; set; }
public int PhysicalLayout { get; set; }
public string LogicalLayout { get; set; }
public string LayoutType { get; set; }
public string LayoutParameter { get; set; }
public string CustomLayoutPath { get; set; }
public List<DeviceInputIdentifierEntity> InputIdentifiers { get; set; }
public List<InputMappingEntity> InputMappings { get; set; }

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
namespace Artemis.Storage.Entities.Workshop;
@ -17,6 +16,6 @@ public class EntryEntity
public long ReleaseId { get; set; }
public string ReleaseVersion { get; set; }
public DateTimeOffset InstalledAt { get; set; }
public Dictionary<string,object> Metadata { get; set; }
public string LocalReference { get; set; }
}

View File

@ -1,35 +0,0 @@
using System.Collections.Generic;
using Artemis.Storage.Migrations.Interfaces;
using LiteDB;
namespace Artemis.Storage.Migrations;
public class M0023LayoutProviders : IStorageMigration
{
public int UserVersion => 23;
public void Apply(LiteRepository repository)
{
ILiteCollection<BsonDocument> deviceEntities = repository.Database.GetCollection("DeviceEntity");
List<BsonDocument> toUpdate = new();
foreach (BsonDocument bsonDocument in deviceEntities.FindAll())
{
if (bsonDocument.TryGetValue("CustomLayoutPath", out BsonValue customLayoutPath) && customLayoutPath.IsString && !string.IsNullOrEmpty(customLayoutPath.AsString))
{
bsonDocument.Add("LayoutType", new BsonValue("CustomPath"));
bsonDocument.Add("LayoutParameter", new BsonValue(customLayoutPath.AsString));
}
else if (bsonDocument.TryGetValue("DisableDefaultLayout", out BsonValue disableDefaultLayout) && disableDefaultLayout.AsBoolean)
bsonDocument.Add("LayoutType", new BsonValue("None"));
else
bsonDocument.Add("LayoutType", new BsonValue("Default"));
bsonDocument.Remove("CustomLayoutPath");
bsonDocument.Remove("DisableDefaultLayout");
toUpdate.Add(bsonDocument);
}
deviceEntities.Update(toUpdate);
}
}

View File

@ -1,4 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Artemis.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>

View File

@ -1,4 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Artemis.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>

View File

@ -1,4 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Artemis.props" />
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net7.0</TargetFramework>
@ -10,16 +12,16 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.6" />
<PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.6" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.6" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.6" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.6" />
<PackageReference Include="DynamicData" Version="8.3.27" />
<PackageReference Include="FluentAvaloniaUI" Version="2.0.5" />
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.0" />
<PackageReference Include="ReactiveUI" Version="19.5.39" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.2" />
<PackageReference Include="DynamicData" Version="8.0.2" />
<PackageReference Include="FluentAvaloniaUI" Version="2.0.4" />
<PackageReference Include="Material.Icons.Avalonia" Version="2.0.1" />
<PackageReference Include="ReactiveUI" Version="19.5.1" />
<PackageReference Include="ReactiveUI.Validation" Version="3.1.7" />
</ItemGroup>
<ItemGroup>

View File

@ -107,7 +107,7 @@ public class ContentDialogBuilder
_contentDialog.IsSecondaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
builder.Command.CanExecuteChanged += (_, _) => _contentDialog.IsSecondaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
}
return this;
}

View File

@ -4,7 +4,7 @@
<StackPanel>
<Border Classes="card" Margin="20">
<TextBlock>I'm in a panel yo!</TextBlock>
</Border>
</Border>
<Border Classes="card" Margin="20">
<StackPanel>
<TextBlock>I'm in a panel yo!</TextBlock>

View File

@ -27,7 +27,22 @@
<Setter Property="Maximum" Value="{x:Static system:Double.MaxValue}"></Setter>
<Setter Property="Minimum" Value="{x:Static system:Double.MinValue}"></Setter>
</Style>
<Style Selector="controls|NumberBox /template/ TextBox#InputBox">
<Style Selector="controls|NumberBox /template/ TextBox.NumberBoxTextBoxStyle /template/ TextBlock#PART_Prefix">
<Setter Property="Foreground" Value="{DynamicResource TextControlForegroundDisabled}"></Setter>
<Setter Property="Margin" Value="-4 0 -12 0"></Setter>
</Style>
<Style Selector="controls|NumberBox /template/ TextBox.NumberBoxTextBoxStyle /template/ TextBlock#PART_Suffix">
<Setter Property="Foreground" Value="{DynamicResource TextControlForegroundDisabled}"></Setter>
<Setter Property="Margin" Value="-12 0 0 0"></Setter>
</Style>
<Style Selector="controls|NumberBox.condensed /template/ TextBox.NumberBoxTextBoxStyle /template/ TextBlock#PART_Prefix">
<Setter Property="Margin" Value="0 0 -4 0"></Setter>
</Style>
<Style Selector="controls|NumberBox.condensed /template/ TextBox.NumberBoxTextBoxStyle /template/ TextBlock#PART_Suffix">
<Setter Property="Margin" Value="-4 0 0 0"></Setter>
</Style>
<Style Selector="controls|NumberBox /template/ TextBox.NumberBoxTextBoxStyle">
<Setter Property="attachedProperties:TextBoxAssist.PrefixText" Value="{TemplateBinding attachedProperties:NumberBoxAssist.PrefixText}"></Setter>
<Setter Property="attachedProperties:TextBoxAssist.SuffixText" Value="{TemplateBinding attachedProperties:NumberBoxAssist.SuffixText}"></Setter>
</Style>

View File

@ -5,21 +5,7 @@
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Spacing="20">
<TextBox Width="200" Height="150" AcceptsReturn="True" xml:space="preserve">asdasdas asd
asdasd
asd
asd
asd
as
fdsfsdf
sdg
sdg
sdg
</TextBox>
<TextBox Width="200" Height="150" AcceptsReturn="True" xml:space="preserve">asdasdas asd
asdasd
asd
as</TextBox>
<!-- Add Controls for Previewer Here -->
<TextBox Text="99999999"
@ -60,10 +46,12 @@ as</TextBox>
<Border Margin="{TemplateBinding BorderThickness}">
<Grid ColumnDefinitions="Auto,*,Auto">
<ContentPresenter Grid.Column="0" Content="{TemplateBinding InnerLeftContent}" />
<ContentPresenter Grid.Column="0"
Grid.ColumnSpan="1"
Content="{TemplateBinding InnerLeftContent}" />
<Grid x:Name="PART_InnerGrid"
Grid.Column="1"
RowDefinitions="Auto,*"
RowDefinitions="Auto,Auto"
ColumnDefinitions="Auto,*,Auto"
Cursor="IBeam"
Margin="{TemplateBinding Padding}">

View File

@ -0,0 +1,119 @@
// Heavily based on:
// SkyClip
// - ProgressableStreamContent.cs
// --------------------------------------------------------------------
// Author: Jeff Hansen <jeff@jeffijoe.com>
// Copyright (C) Jeff Hansen 2015. All rights reserved.
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace Artemis.UI.Shared.Utilities;
/// <summary>
/// Provides HTTP content based on a stream with support for IProgress.
/// </summary>
public class ProgressableStreamContent : StreamContent
{
private const int DEFAULT_BUFFER_SIZE = 4096;
private readonly int _bufferSize;
private readonly IProgress<StreamProgress> _progress;
private readonly Stream _streamToWrite;
private bool _contentConsumed;
/// <summary>
/// Initializes a new instance of the <see cref="ProgressableStreamContent" /> class.
/// </summary>
/// <param name="streamToWrite">The stream to write.</param>
/// <param name="progress">The downloader.</param>
public ProgressableStreamContent(Stream streamToWrite, IProgress<StreamProgress> progress) : this(streamToWrite, DEFAULT_BUFFER_SIZE, progress)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ProgressableStreamContent" /> class.
/// </summary>
/// <param name="streamToWrite">The stream to write.</param>
/// <param name="bufferSize">The buffer size.</param>
/// <param name="progress">The downloader.</param>
public ProgressableStreamContent(Stream streamToWrite, int bufferSize, IProgress<StreamProgress> progress) : base(streamToWrite, bufferSize)
{
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException(nameof(bufferSize));
_streamToWrite = streamToWrite;
_bufferSize = bufferSize;
_progress = progress;
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
_streamToWrite.Dispose();
base.Dispose(disposing);
}
/// <inheritdoc />
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context)
{
await SerializeToStreamAsync(stream, context, CancellationToken.None);
}
/// <inheritdoc />
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken)
{
PrepareContent();
byte[] buffer = new byte[_bufferSize];
long size = _streamToWrite.Length;
int uploaded = 0;
await using (_streamToWrite)
{
while (!cancellationToken.IsCancellationRequested)
{
int length = await _streamToWrite.ReadAsync(buffer, cancellationToken);
if (length <= 0)
break;
uploaded += length;
_progress.Report(new StreamProgress(uploaded, size));
await stream.WriteAsync(buffer, 0, length, cancellationToken);
}
}
}
/// <inheritdoc />
protected override bool TryComputeLength(out long length)
{
length = _streamToWrite.Length;
return true;
}
/// <summary>
/// Prepares the content.
/// </summary>
/// <exception cref="System.InvalidOperationException">The stream has already been read.</exception>
private void PrepareContent()
{
if (_contentConsumed)
{
// If the content needs to be written to a target stream a 2nd time, then the stream must support
// seeking (e.g. a FileStream), otherwise the stream can't be copied a second time to a target
// stream (e.g. a NetworkStream).
if (_streamToWrite.CanSeek)
_streamToWrite.Position = 0;
else
throw new InvalidOperationException("The stream has already been read.");
}
_contentConsumed = true;
}
}

View File

@ -1,4 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Artemis.props" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows10.0.17763.0</TargetFramework>
@ -21,13 +23,12 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Win32" Version="11.0.6" />
<PackageReference Include="Avalonia.Win32" Version="$(AvaloniaVersion)" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageReference Include="Microsoft.Win32" Version="2.0.1" />
<!-- Note: Do NOT upgrade this compatibility package to 8.X before updating to net8, it WILL break -->
<PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.5" />
<PackageReference Include="RawInput.Sharp" Version="0.1.3" />
<PackageReference Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.7" />
<PackageReference Include="RawInput.Sharp" Version="0.1.1" />
<PackageReference Include="SkiaSharp.Vulkan.SharpVk" Version="$(SkiaSharpVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />

View File

@ -1,4 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Artemis.props" />
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net7.0</TargetFramework>
@ -17,18 +19,18 @@
<ItemGroup>
<PackageReference Include="AsyncImageLoader.Avalonia" Version="3.2.1" />
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.6" />
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="11.0.0.2" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.6" />
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.1" />
<PackageReference Include="Avalonia.Controls.PanAndZoom" Version="11.0.0" />
<PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
<PackageReference Include="Avalonia.Skia.Lottie" Version="11.0.0" />
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.6" />
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.1" />
<PackageReference Include="Markdown.Avalonia.Tight" Version="11.0.2" />
<PackageReference Include="Octopus.Octodiff" Version="2.0.544" />
<PackageReference Include="PropertyChanged.SourceGenerator" Version="1.1.0">
<PackageReference Include="Octopus.Octodiff" Version="2.0.468" />
<PackageReference Include="PropertyChanged.SourceGenerator" Version="1.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Splat.DryIoc" Version="14.8.12" />
<PackageReference Include="Splat.DryIoc" Version="14.7.1" />
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.56" />
</ItemGroup>

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,6 @@
using System.Reflection;
using Artemis.UI.DryIoc.Factories;
using Artemis.UI.DryIoc.InstanceProviders;
using Artemis.UI.Screens.Device.Layout.LayoutProviders;
using Artemis.UI.Screens.VisualScripting;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Services.Updating;
@ -27,7 +26,6 @@ public static class ContainerExtensions
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<ViewModelBase>(), setup: Setup.With(preventDisposal: true));
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IToolViewModel>() && type.IsInterface, setup: Setup.With(preventDisposal: true));
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<ILayoutProviderViewModel>() && type.IsInterface, setup: Setup.With(preventDisposal: true));
container.RegisterMany(thisAssembly, type => type.IsAssignableTo<IVmFactory>() && type != typeof(PropertyVmFactory));
container.Register<NodeScriptWindowViewModelBase, NodeScriptWindowViewModel>(Reuse.Singleton);

View File

@ -1,14 +1,13 @@
using System.Collections.ObjectModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reactive;
using Artemis.Core;
using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects;
using Artemis.Core.ScriptingProviders;
using Artemis.UI.Routing;
using Artemis.UI.Screens.Device;
using Artemis.UI.Screens.Device.General;
using Artemis.UI.Screens.Device.InputMappings;
using Artemis.UI.Screens.Device.Layout;
using Artemis.UI.Screens.Device.Leds;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Screens.Plugins.Features;
using Artemis.UI.Screens.Plugins.Prerequisites;
@ -28,8 +27,12 @@ using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Screens.SurfaceEditor;
using Artemis.UI.Screens.VisualScripting;
using Artemis.UI.Screens.VisualScripting.Pins;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Updating;
using DryIoc;
using DynamicData;
using Material.Icons;
using ReactiveUI;
namespace Artemis.UI.DryIoc.Factories;
@ -48,49 +51,48 @@ public interface IDeviceVmFactory : IVmFactory
InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds);
DeviceGeneralTabViewModel DeviceGeneralTabViewModel(ArtemisDevice device);
}
public class DeviceFactory : IDeviceVmFactory
{
private readonly IContainer _container;
public DeviceFactory(IContainer container)
{
_container = container;
}
public DevicePropertiesViewModel DevicePropertiesViewModel(ArtemisDevice device)
{
return _container.Resolve<DevicePropertiesViewModel>(new object[] {device});
return _container.Resolve<DevicePropertiesViewModel>(new object[] { device });
}
public DeviceSettingsViewModel DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel)
{
return _container.Resolve<DeviceSettingsViewModel>(new object[] {device, devicesTabViewModel});
return _container.Resolve<DeviceSettingsViewModel>(new object[] { device, devicesTabViewModel });
}
public DeviceDetectInputViewModel DeviceDetectInputViewModel(ArtemisDevice device)
{
return _container.Resolve<DeviceDetectInputViewModel>(new object[] {device});
return _container.Resolve<DeviceDetectInputViewModel>(new object[] { device });
}
public DeviceLayoutTabViewModel DeviceLayoutTabViewModel(ArtemisDevice device)
{
return _container.Resolve<DeviceLayoutTabViewModel>(new object[] {device});
return _container.Resolve<DeviceLayoutTabViewModel>(new object[] { device });
}
public DeviceLedsTabViewModel DeviceLedsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds)
{
return _container.Resolve<DeviceLedsTabViewModel>(new object[] {device, selectedLeds});
return _container.Resolve<DeviceLedsTabViewModel>(new object[] { device, selectedLeds });
}
public InputMappingsTabViewModel InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds)
{
return _container.Resolve<InputMappingsTabViewModel>(new object[] {device, selectedLeds});
return _container.Resolve<InputMappingsTabViewModel>(new object[] { device, selectedLeds });
}
public DeviceGeneralTabViewModel DeviceGeneralTabViewModel(ArtemisDevice device)
{
return _container.Resolve<DeviceGeneralTabViewModel>(new object[] {device});
return _container.Resolve<DeviceGeneralTabViewModel>(new object[] { device });
}
}
@ -100,29 +102,28 @@ public interface ISettingsVmFactory : IVmFactory
PluginViewModel PluginViewModel(Plugin plugin, ReactiveCommand<Unit, Unit>? reload);
PluginFeatureViewModel PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield);
}
public class SettingsVmFactory : ISettingsVmFactory
{
private readonly IContainer _container;
public SettingsVmFactory(IContainer container)
{
_container = container;
}
public PluginSettingsViewModel PluginSettingsViewModel(Plugin plugin)
{
return _container.Resolve<PluginSettingsViewModel>(new object[] {plugin});
return _container.Resolve<PluginSettingsViewModel>(new object[] { plugin });
}
public PluginViewModel PluginViewModel(Plugin plugin, ReactiveCommand<Unit, Unit>? reload)
{
return _container.Resolve<PluginViewModel>(new object?[] {plugin, reload});
return _container.Resolve<PluginViewModel>(new object?[] { plugin, reload });
}
public PluginFeatureViewModel PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield)
{
return _container.Resolve<PluginFeatureViewModel>(new object[] {pluginFeatureInfo, showShield});
return _container.Resolve<PluginFeatureViewModel>(new object[] { pluginFeatureInfo, showShield });
}
}
@ -131,24 +132,23 @@ public interface ISidebarVmFactory : IVmFactory
SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory);
SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration);
}
public class SidebarVmFactory : ISidebarVmFactory
{
private readonly IContainer _container;
public SidebarVmFactory(IContainer container)
{
_container = container;
}
public SidebarCategoryViewModel SidebarCategoryViewModel(ProfileCategory profileCategory)
{
return _container.Resolve<SidebarCategoryViewModel>(new object[] {profileCategory});
return _container.Resolve<SidebarCategoryViewModel>(new object[] { profileCategory });
}
public SidebarProfileConfigurationViewModel SidebarProfileConfigurationViewModel(ProfileConfiguration profileConfiguration)
{
return _container.Resolve<SidebarProfileConfigurationViewModel>(new object[] {profileConfiguration});
return _container.Resolve<SidebarProfileConfigurationViewModel>(new object[] { profileConfiguration });
}
}
@ -157,7 +157,6 @@ public interface ISurfaceVmFactory : IVmFactory
SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel);
ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel);
}
public class SurfaceVmFactory : ISurfaceVmFactory
{
private readonly IContainer _container;
@ -169,12 +168,12 @@ public class SurfaceVmFactory : ISurfaceVmFactory
public SurfaceDeviceViewModel SurfaceDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel)
{
return _container.Resolve<SurfaceDeviceViewModel>(new object[] {device, surfaceEditorViewModel});
return _container.Resolve<SurfaceDeviceViewModel>(new object[] { device, surfaceEditorViewModel });
}
public ListDeviceViewModel ListDeviceViewModel(ArtemisDevice device, SurfaceEditorViewModel surfaceEditorViewModel)
{
return _container.Resolve<ListDeviceViewModel>(new object[] {device, surfaceEditorViewModel});
return _container.Resolve<ListDeviceViewModel>(new object[] { device, surfaceEditorViewModel });
}
}
@ -182,7 +181,6 @@ public interface IPrerequisitesVmFactory : IVmFactory
{
PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall);
}
public class PrerequisitesVmFactory : IPrerequisitesVmFactory
{
private readonly IContainer _container;
@ -194,7 +192,7 @@ public class PrerequisitesVmFactory : IPrerequisitesVmFactory
public PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall)
{
return _container.Resolve<PluginPrerequisiteViewModel>(new object[] {pluginPrerequisite, uninstall});
return _container.Resolve<PluginPrerequisiteViewModel>(new object[] { pluginPrerequisite, uninstall });
}
}
@ -206,7 +204,6 @@ public interface IProfileEditorVmFactory : IVmFactory
LayerShapeVisualizerViewModel LayerShapeVisualizerViewModel(Layer layer);
LayerVisualizerViewModel LayerVisualizerViewModel(Layer layer);
}
public class ProfileEditorVmFactory : IProfileEditorVmFactory
{
private readonly IContainer _container;
@ -218,27 +215,27 @@ public class ProfileEditorVmFactory : IProfileEditorVmFactory
public FolderTreeItemViewModel FolderTreeItemViewModel(TreeItemViewModel? parent, Folder folder)
{
return _container.Resolve<FolderTreeItemViewModel>(new object?[] {parent, folder});
return _container.Resolve<FolderTreeItemViewModel>(new object?[] { parent, folder });
}
public LayerShapeVisualizerViewModel LayerShapeVisualizerViewModel(Layer layer)
{
return _container.Resolve<LayerShapeVisualizerViewModel>(new object[] {layer});
return _container.Resolve<LayerShapeVisualizerViewModel>(new object[] { layer });
}
public LayerTreeItemViewModel LayerTreeItemViewModel(TreeItemViewModel? parent, Layer layer)
{
return _container.Resolve<LayerTreeItemViewModel>(new object?[] {parent, layer});
return _container.Resolve<LayerTreeItemViewModel>(new object?[] { parent, layer });
}
public LayerVisualizerViewModel LayerVisualizerViewModel(Layer layer)
{
return _container.Resolve<LayerVisualizerViewModel>(new object[] {layer});
return _container.Resolve<LayerVisualizerViewModel>(new object[] { layer });
}
public ProfileEditorViewModel ProfileEditorViewModel(IScreen hostScreen)
{
return _container.Resolve<ProfileEditorViewModel>(new object[] {hostScreen});
return _container.Resolve<ProfileEditorViewModel>(new object[] { hostScreen });
}
}
@ -254,7 +251,6 @@ public interface ILayerPropertyVmFactory : IVmFactory
TimelineViewModel TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels);
TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
}
public class LayerPropertyVmFactory : ILayerPropertyVmFactory
{
private readonly IContainer _container;
@ -266,37 +262,37 @@ public class LayerPropertyVmFactory : ILayerPropertyVmFactory
public PropertyViewModel PropertyViewModel(ILayerProperty layerProperty)
{
return _container.Resolve<PropertyViewModel>(new object[] {layerProperty});
return _container.Resolve<PropertyViewModel>(new object[] { layerProperty });
}
public PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup)
{
return _container.Resolve<PropertyGroupViewModel>(new object[] {layerPropertyGroup});
return _container.Resolve<PropertyGroupViewModel>(new object[] { layerPropertyGroup });
}
public PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush layerBrush)
{
return _container.Resolve<PropertyGroupViewModel>(new object[] {layerPropertyGroup, layerBrush});
return _container.Resolve<PropertyGroupViewModel>(new object[] { layerPropertyGroup, layerBrush });
}
public PropertyGroupViewModel PropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerEffect layerEffect)
{
return _container.Resolve<PropertyGroupViewModel>(new object[] {layerPropertyGroup, layerEffect});
return _container.Resolve<PropertyGroupViewModel>(new object[] { layerPropertyGroup, layerEffect });
}
public TreeGroupViewModel TreeGroupViewModel(PropertyGroupViewModel propertyGroupViewModel)
{
return _container.Resolve<TreeGroupViewModel>(new object[] {propertyGroupViewModel});
return _container.Resolve<TreeGroupViewModel>(new object[] { propertyGroupViewModel });
}
public TimelineViewModel TimelineViewModel(ObservableCollection<PropertyGroupViewModel> propertyGroupViewModels)
{
return _container.Resolve<TimelineViewModel>(new object[] {propertyGroupViewModels});
return _container.Resolve<TimelineViewModel>(new object[] { propertyGroupViewModels });
}
public TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel)
{
return _container.Resolve<TimelineGroupViewModel>(new object[] {propertyGroupViewModel});
return _container.Resolve<TimelineGroupViewModel>(new object[] { propertyGroupViewModel });
}
}
@ -304,7 +300,6 @@ public interface IDataBindingVmFactory : IVmFactory
{
DataBindingViewModel DataBindingViewModel();
}
public class DataBindingVmFactory : IDataBindingVmFactory
{
private readonly IContainer _container;
@ -338,7 +333,6 @@ public interface INodeVmFactory : IVmFactory
InputPinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel);
OutputPinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel);
}
public class NodeVmFactory : INodeVmFactory
{
private readonly IContainer _container;
@ -350,47 +344,47 @@ public class NodeVmFactory : INodeVmFactory
public NodeScriptViewModel NodeScriptViewModel(NodeScript nodeScript, bool isPreview)
{
return _container.Resolve<NodeScriptViewModel>(new object[] {nodeScript, isPreview});
return _container.Resolve<NodeScriptViewModel>(new object[] { nodeScript, isPreview });
}
public NodePickerViewModel NodePickerViewModel(NodeScript nodeScript)
{
return _container.Resolve<NodePickerViewModel>(new object[] {nodeScript});
return _container.Resolve<NodePickerViewModel>(new object[] { nodeScript });
}
public NodeViewModel NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node)
{
return _container.Resolve<NodeViewModel>(new object[] {nodeScriptViewModel, node});
return _container.Resolve<NodeViewModel>(new object[] { nodeScriptViewModel, node });
}
public CableViewModel CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to)
{
return _container.Resolve<CableViewModel>(new object[] {nodeScriptViewModel, from, to});
return _container.Resolve<CableViewModel>(new object[] { nodeScriptViewModel, from, to });
}
public DragCableViewModel DragCableViewModel(PinViewModel pinViewModel)
{
return _container.Resolve<DragCableViewModel>(new object[] {pinViewModel});
return _container.Resolve<DragCableViewModel>(new object[] { pinViewModel });
}
public InputPinViewModel InputPinViewModel(IPin inputPin, NodeScriptViewModel nodeScriptViewModel)
{
return _container.Resolve<InputPinViewModel>(new object[] {inputPin, nodeScriptViewModel});
return _container.Resolve<InputPinViewModel>(new object[] { inputPin, nodeScriptViewModel });
}
public OutputPinViewModel OutputPinViewModel(IPin outputPin, NodeScriptViewModel nodeScriptViewModel)
{
return _container.Resolve<OutputPinViewModel>(new object[] {outputPin, nodeScriptViewModel});
return _container.Resolve<OutputPinViewModel>(new object[] { outputPin, nodeScriptViewModel });
}
public InputPinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel)
{
return _container.Resolve<InputPinCollectionViewModel>(new object[] {inputPinCollection, nodeScriptViewModel});
return _container.Resolve<InputPinCollectionViewModel>(new object[] { inputPinCollection, nodeScriptViewModel });
}
public OutputPinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel)
{
return _container.Resolve<OutputPinCollectionViewModel>(new object[] {outputPinCollection, nodeScriptViewModel});
return _container.Resolve<OutputPinCollectionViewModel>(new object[] { outputPinCollection, nodeScriptViewModel });
}
}
@ -401,7 +395,6 @@ public interface IConditionVmFactory : IVmFactory
StaticConditionViewModel StaticConditionViewModel(StaticCondition staticCondition);
EventConditionViewModel EventConditionViewModel(EventCondition eventCondition);
}
public class ConditionVmFactory : IConditionVmFactory
{
private readonly IContainer _container;
@ -413,22 +406,22 @@ public class ConditionVmFactory : IConditionVmFactory
public AlwaysOnConditionViewModel AlwaysOnConditionViewModel(AlwaysOnCondition alwaysOnCondition)
{
return _container.Resolve<AlwaysOnConditionViewModel>(new object[] {alwaysOnCondition});
return _container.Resolve<AlwaysOnConditionViewModel>(new object[] { alwaysOnCondition });
}
public PlayOnceConditionViewModel PlayOnceConditionViewModel(PlayOnceCondition playOnceCondition)
{
return _container.Resolve<PlayOnceConditionViewModel>(new object[] {playOnceCondition});
return _container.Resolve<PlayOnceConditionViewModel>(new object[] { playOnceCondition });
}
public StaticConditionViewModel StaticConditionViewModel(StaticCondition staticCondition)
{
return _container.Resolve<StaticConditionViewModel>(new object[] {staticCondition});
return _container.Resolve<StaticConditionViewModel>(new object[] { staticCondition });
}
public EventConditionViewModel EventConditionViewModel(EventCondition eventCondition)
{
return _container.Resolve<EventConditionViewModel>(new object[] {eventCondition});
return _container.Resolve<EventConditionViewModel>(new object[] { eventCondition });
}
}
@ -439,7 +432,6 @@ public interface ILayerHintVmFactory : IVmFactory
KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(Layer layer, KeyboardSectionAdaptionHint adaptionHint);
SingleLedAdaptionHintViewModel SingleLedAdaptionHintViewModel(Layer layer, SingleLedAdaptionHint adaptionHint);
}
public class LayerHintVmFactory : ILayerHintVmFactory
{
private readonly IContainer _container;
@ -451,22 +443,22 @@ public class LayerHintVmFactory : ILayerHintVmFactory
public CategoryAdaptionHintViewModel CategoryAdaptionHintViewModel(Layer layer, CategoryAdaptionHint adaptionHint)
{
return _container.Resolve<CategoryAdaptionHintViewModel>(new object[] {layer, adaptionHint});
return _container.Resolve<CategoryAdaptionHintViewModel>(new object[] { layer, adaptionHint });
}
public DeviceAdaptionHintViewModel DeviceAdaptionHintViewModel(Layer layer, DeviceAdaptionHint adaptionHint)
{
return _container.Resolve<DeviceAdaptionHintViewModel>(new object[] {layer, adaptionHint});
return _container.Resolve<DeviceAdaptionHintViewModel>(new object[] { layer, adaptionHint });
}
public KeyboardSectionAdaptionHintViewModel KeyboardSectionAdaptionHintViewModel(Layer layer, KeyboardSectionAdaptionHint adaptionHint)
{
return _container.Resolve<KeyboardSectionAdaptionHintViewModel>(new object[] {layer, adaptionHint});
return _container.Resolve<KeyboardSectionAdaptionHintViewModel>(new object[] { layer, adaptionHint });
}
public SingleLedAdaptionHintViewModel SingleLedAdaptionHintViewModel(Layer layer, SingleLedAdaptionHint adaptionHint)
{
return _container.Resolve<SingleLedAdaptionHintViewModel>(new object[] {layer, adaptionHint});
return _container.Resolve<SingleLedAdaptionHintViewModel>(new object[] { layer, adaptionHint });
}
}
@ -475,7 +467,6 @@ public interface IScriptVmFactory : IVmFactory
ScriptConfigurationViewModel ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration);
ScriptConfigurationViewModel ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration);
}
public class ScriptVmFactory : IScriptVmFactory
{
private readonly IContainer _container;
@ -487,12 +478,12 @@ public class ScriptVmFactory : IScriptVmFactory
public ScriptConfigurationViewModel ScriptConfigurationViewModel(ScriptConfiguration scriptConfiguration)
{
return _container.Resolve<ScriptConfigurationViewModel>(new object[] {scriptConfiguration});
return _container.Resolve<ScriptConfigurationViewModel>(new object[] { scriptConfiguration });
}
public ScriptConfigurationViewModel ScriptConfigurationViewModel(Profile profile, ScriptConfiguration scriptConfiguration)
{
return _container.Resolve<ScriptConfigurationViewModel>(new object[] {profile, scriptConfiguration});
return _container.Resolve<ScriptConfigurationViewModel>(new object[] { profile, scriptConfiguration });
}
}
@ -500,7 +491,6 @@ public interface IReleaseVmFactory : IVmFactory
{
ReleaseViewModel ReleaseListViewModel(IGetReleases_PublishedReleases_Nodes release);
}
public class ReleaseVmFactory : IReleaseVmFactory
{
private readonly IContainer _container;
@ -509,9 +499,9 @@ public class ReleaseVmFactory : IReleaseVmFactory
{
_container = container;
}
public ReleaseViewModel ReleaseListViewModel(IGetReleases_PublishedReleases_Nodes release)
{
return _container.Resolve<ReleaseViewModel>(new object[] {release});
return _container.Resolve<ReleaseViewModel>(new object[] { release });
}
}

View File

@ -6,7 +6,6 @@ using Artemis.UI.Screens.Debugger.Logs;
using Artemis.UI.Screens.Debugger.Performance;
using Artemis.UI.Screens.Debugger.Render;
using Artemis.UI.Screens.Debugger.Routing;
using Artemis.UI.Screens.Debugger.Workshop;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
using PropertyChanged.SourceGenerator;
@ -18,9 +17,9 @@ public partial class DebugViewModel : ActivatableViewModelBase, IScreen
{
[Notify] private ViewModelBase _selectedItem;
public DebugViewModel(IDebugService debugService, RenderDebugViewModel render, DataModelDebugViewModel dataModel, PerformanceDebugViewModel performance, RoutingDebugViewModel routing, WorkshopDebugViewModel workshop, LogsDebugViewModel logs)
public DebugViewModel(IDebugService debugService, RenderDebugViewModel render, DataModelDebugViewModel dataModel, PerformanceDebugViewModel performance, RoutingDebugViewModel routing, LogsDebugViewModel logs)
{
Items = new ObservableCollection<ViewModelBase> {render, dataModel, performance, routing, workshop, logs};
Items = new ObservableCollection<ViewModelBase> {render, dataModel, performance, routing, logs};
_selectedItem = render;
this.WhenActivated(d => Disposable.Create(debugService.ClearDebugger).DisposeWith(d));

View File

@ -1,32 +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:workshop="clr-namespace:Artemis.UI.Screens.Debugger.Workshop"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Debugger.Workshop.WorkshopDebugView"
x:DataType="workshop:WorkshopDebugViewModel">
<ScrollViewer Classes="with-padding">
<StackPanel>
<Label>Workshop Status</Label>
<Border Classes="card-condensed">
<SelectableTextBlock Text="{CompiledBinding WorkshopStatus}" FontFamily="{StaticResource RobotoMono}" FontSize="13" />
</Border>
<Label Margin="0 10 0 0">Auth token (DO NOT SHARE)</Label>
<Border Classes="card-condensed">
<SelectableTextBlock Text="{CompiledBinding Token}" FontFamily="{StaticResource RobotoMono}" FontSize="13" TextWrapping="Wrap" />
</Border>
<Label Margin="0 10 0 0">Email verified</Label>
<Border Classes="card-condensed">
<SelectableTextBlock Text="{CompiledBinding EmailVerified}" FontFamily="{StaticResource RobotoMono}" FontSize="13" />
</Border>
<Label Margin="0 10 0 0">Claims</Label>
<Border Classes="card-condensed">
<SelectableTextBlock Text="{CompiledBinding Claims}" FontFamily="{StaticResource RobotoMono}" FontSize="13" />
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@ -1,14 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Debugger.Workshop;
public partial class WorkshopDebugView : ReactiveUserControl<WorkshopDebugViewModel>
{
public WorkshopDebugView()
{
InitializeComponent();
}
}

View File

@ -1,30 +0,0 @@
using System.Threading;
using Artemis.UI.Extensions;
using Artemis.UI.Shared;
using Artemis.WebClient.Workshop.Services;
using Newtonsoft.Json;
using PropertyChanged.SourceGenerator;
namespace Artemis.UI.Screens.Debugger.Workshop;
public partial class WorkshopDebugViewModel : ActivatableViewModelBase
{
[Notify] private string? _token;
[Notify] private bool _emailVerified;
[Notify] private string? _claims;
[Notify] private IWorkshopService.WorkshopStatus? _workshopStatus;
public WorkshopDebugViewModel(IWorkshopService workshopService, IAuthenticationService authenticationService)
{
DisplayName = "Workshop";
this.WhenActivatedAsync(async _ =>
{
Token = await authenticationService.GetBearer();
EmailVerified = authenticationService.GetIsEmailVerified();
Claims = JsonConvert.SerializeObject(authenticationService.Claims, Formatting.Indented);
WorkshopStatus = await workshopService.GetWorkshopStatus(CancellationToken.None);
});
}
}

View File

@ -5,10 +5,9 @@
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"
x:DataType="general:DeviceGeneralTabViewModel">
x:Class="Artemis.UI.Screens.Device.DeviceGeneralTabView"
x:DataType="device:DeviceGeneralTabViewModel">
<UserControl.Resources>
<converters:SKColorToColorConverter x:Key="SKColorToColorConverter" />
</UserControl.Resources>

View File

@ -1,6 +1,7 @@
using Avalonia.Controls;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device.General;
namespace Artemis.UI.Screens.Device;
public partial class DeviceGeneralTabView : ReactiveUserControl<DeviceGeneralTabViewModel>
{
public DeviceGeneralTabView()

View File

@ -4,7 +4,6 @@ using System.Reactive.Disposables;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Screens.Device.Layout;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using PropertyChanged.SourceGenerator;
@ -12,7 +11,7 @@ using ReactiveUI;
using RGB.NET.Core;
using SkiaSharp;
namespace Artemis.UI.Screens.Device.General;
namespace Artemis.UI.Screens.Device;
public partial class DeviceGeneralTabViewModel : ActivatableViewModelBase
{
@ -57,7 +56,7 @@ public partial class DeviceGeneralTabViewModel : ActivatableViewModelBase
_initialGreenScale = Device.GreenScale;
_initialBlueScale = Device.BlueScale;
this.WhenAnyValue<DeviceGeneralTabViewModel, float, float, float>(x => x.RedScale, x => x.GreenScale, x => x.BlueScale).Subscribe(_ => ApplyScaling());
this.WhenAnyValue(x => x.RedScale, x => x.GreenScale, x => x.BlueScale).Subscribe(_ => ApplyScaling());
this.WhenActivated(d =>
{
@ -128,12 +127,12 @@ public partial class DeviceGeneralTabViewModel : ActivatableViewModelBase
return;
if (!Device.DeviceProvider.CanDetectPhysicalLayout && !await DevicePhysicalLayoutDialogViewModel.SelectPhysicalLayout(_windowService, Device))
return;
if (!Device.DeviceProvider.CanDetectLogicalLayout && !await Layout.DeviceLogicalLayoutDialogViewModel.SelectLogicalLayout(_windowService, Device))
if (!Device.DeviceProvider.CanDetectLogicalLayout && !await DeviceLogicalLayoutDialogViewModel.SelectLogicalLayout(_windowService, Device))
return;
await Task.Delay(400);
_deviceService.SaveDevice(Device);
_deviceService.LoadDeviceLayout(Device);
_deviceService.ApplyDeviceLayout(Device, Device.GetBestDeviceLayout());
}
private void Apply()

View File

@ -4,11 +4,10 @@
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:layout="clr-namespace:Artemis.UI.Screens.Device.Layout"
xmlns:layoutProviders="clr-namespace:Artemis.UI.Screens.Device.Layout.LayoutProviders"
xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
x:Class="Artemis.UI.Screens.Device.Layout.DeviceLayoutTabView"
x:DataType="layout:DeviceLayoutTabViewModel">
x:Class="Artemis.UI.Screens.Device.DeviceLayoutTabView"
x:DataType="device:DeviceLayoutTabViewModel">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<Border Classes="card" Margin="5">
<Grid RowDefinitions="*,Auto">
@ -29,7 +28,6 @@
</Button>
</StackPanel>
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0">
@ -47,43 +45,28 @@
</Button>
</StackPanel>
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*,*" ColumnDefinitions="*,Auto">
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0">
<TextBlock Text="Layout provider" />
<TextBlock Classes="subtitle" FontSize="12" Text="Choose between different ways to load a layout for this device." />
<TextBlock Text="Disable default layout" />
<TextBlock Classes="subtitle" FontSize="12" Text="With this checked, Artemis will not load a layout for this device unless you specifically provide one." />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center">
<StackPanel.Styles>
<Style Selector="ComboBox.layoutProvider /template/ ContentControl#ContentPresenter">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate x:DataType="layoutProviders:ILayoutProviderViewModel">
<TextBlock Text="{CompiledBinding Name}" TextWrapping="Wrap" MaxWidth="350" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Styles>
<ComboBox Classes="layoutProvider"
Width="150"
SelectedItem="{CompiledBinding SelectedLayoutProvider}"
ItemsSource="{CompiledBinding LayoutProviders}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="layoutProviders:ILayoutProviderViewModel">
<StackPanel>
<TextBlock Text="{CompiledBinding Name}" TextWrapping="Wrap" MaxWidth="350" />
<TextBlock Classes="subtitle" Text="{CompiledBinding Description}" TextWrapping="Wrap" MaxWidth="350" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox HorizontalAlignment="Right" Margin="0,0,-10,0" IsChecked="{CompiledBinding Device.DisableDefaultLayout}"/>
</StackPanel>
</Grid>
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0">
<TextBlock Text="Custom layout path" />
<TextBlock Classes="subtitle" FontSize="12" Text="{CompiledBinding CustomLayoutPath, TargetNullValue=None}"/>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
<Button Content="Clear" Command="{CompiledBinding ClearCustomLayout}" IsEnabled="{CompiledBinding HasCustomLayout}" />
<!-- 5 pixels of margin between the buttons -->
<Button Margin="5,0,0,0" Content="Browse" Command="{CompiledBinding BrowseCustomLayout}" />
</StackPanel>
</Grid>
<ContentControl Content="{CompiledBinding SelectedLayoutProvider}" ClipToBounds="False" />
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0">

View File

@ -2,7 +2,7 @@
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device.Layout;
namespace Artemis.UI.Screens.Device;
public partial class DeviceLayoutTabView : ReactiveUserControl<DeviceLayoutTabViewModel>
{

View File

@ -1,60 +1,81 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Artemis.Core;
using Artemis.UI.Screens.Device.Layout.LayoutProviders;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using PropertyChanged.SourceGenerator;
using Avalonia.Controls;
using ReactiveUI;
using RGB.NET.Layout;
using SkiaSharp;
namespace Artemis.UI.Screens.Device.Layout;
namespace Artemis.UI.Screens.Device;
public partial class DeviceLayoutTabViewModel : ActivatableViewModelBase
public class DeviceLayoutTabViewModel : ActivatableViewModelBase
{
[Notify] private ILayoutProviderViewModel? _selectedLayoutProvider;
private readonly IWindowService _windowService;
private readonly INotificationService _notificationService;
private readonly IDeviceService _deviceService;
public DeviceLayoutTabViewModel(IWindowService windowService, INotificationService notificationService, ArtemisDevice device, List<ILayoutProviderViewModel> layoutProviders)
public DeviceLayoutTabViewModel(IWindowService windowService, INotificationService notificationService, IDeviceService deviceService, ArtemisDevice device)
{
_windowService = windowService;
_notificationService = notificationService;
_deviceService = deviceService;
Device = device;
DisplayName = "Layout";
DefaultLayoutPath = Device.DeviceProvider.LoadLayout(Device).FilePath;
LayoutProviders = new ObservableCollection<ILayoutProviderViewModel>(layoutProviders);
foreach (ILayoutProviderViewModel layoutProviderViewModel in layoutProviders)
this.WhenActivated(d =>
{
layoutProviderViewModel.Device = Device;
if (layoutProviderViewModel.Provider.IsMatch(Device))
SelectedLayoutProvider = layoutProviderViewModel;
}
// When changing device provider to one that isn't currently on the device, apply it to the device immediately
this.WhenAnyValue(vm => vm.SelectedLayoutProvider).Subscribe(l =>
{
if (l != null && !l.Provider.IsMatch(Device))
l.Apply();
Device.PropertyChanged += DeviceOnPropertyChanged;
Disposable.Create(() => Device.PropertyChanged -= DeviceOnPropertyChanged).DisposeWith(d);
});
}
public ArtemisDevice Device { get; }
public ObservableCollection<ILayoutProviderViewModel> LayoutProviders { get; set; }
public string DefaultLayoutPath { get; }
public string? ImagePath => Device.Layout?.Image?.LocalPath;
public string? CustomLayoutPath => Device.CustomLayoutPath;
public bool HasCustomLayout => Device.CustomLayoutPath != null;
public void ClearCustomLayout()
{
Device.CustomLayoutPath = null;
_notificationService.CreateNotification()
.WithMessage("Cleared imported layout.")
.WithSeverity(NotificationSeverity.Informational);
}
public async Task BrowseCustomLayout()
{
string[]? files = await _windowService.CreateOpenFileDialog()
.WithTitle("Select device layout file")
.HavingFilter(f => f.WithName("Layout files").WithExtension("xml"))
.ShowAsync();
if (files?.Length > 0)
{
Device.CustomLayoutPath = files[0];
_notificationService.CreateNotification()
.WithTitle("Imported layout")
.WithMessage($"File loaded from {files[0]}")
.WithSeverity(NotificationSeverity.Informational);
}
}
public async Task ExportLayout()
{
string fileName = Device.DeviceProvider.GetDeviceLayoutName(Device);
@ -85,13 +106,13 @@ public partial class DeviceLayoutTabViewModel : ActivatableViewModelBase
}
else
{
List<LedLayout> ledLayouts = Device.Leds.Select(x => new LedLayout
List<LedLayout> ledLayouts = Device.Leds.Select(x => new LedLayout()
{
Id = x.RgbLed.Id.ToString(),
DescriptiveX = x.Rectangle.Left.ToString(),
DescriptiveY = x.Rectangle.Top.ToString(),
DescriptiveWidth = $"{x.Rectangle.Width}mm",
DescriptiveHeight = $"{x.Rectangle.Height}mm"
DescriptiveHeight = $"{x.Rectangle.Height}mm",
}).ToList();
DeviceLayout emptyLayout = new()
@ -102,7 +123,7 @@ public partial class DeviceLayoutTabViewModel : ActivatableViewModelBase
Model = Device.RgbDevice.DeviceInfo.Model,
Width = Device.Rectangle.Width,
Height = Device.Rectangle.Height,
InternalLeds = ledLayouts
InternalLeds = ledLayouts,
};
XmlSerializer serializer = new(typeof(DeviceLayout));
@ -124,4 +145,19 @@ public partial class DeviceLayoutTabViewModel : ActivatableViewModelBase
.WithSeverity(NotificationSeverity.Informational)
.Show();
}
private void DeviceOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName is nameof(Device.CustomLayoutPath) or nameof(Device.DisableDefaultLayout))
{
Task.Run(() =>
{
_deviceService.ApplyDeviceLayout(Device, Device.GetBestDeviceLayout());
_deviceService.SaveDevice(Device);
});
this.RaisePropertyChanged(nameof(CustomLayoutPath));
this.RaisePropertyChanged(nameof(HasCustomLayout));
}
}
}

View File

@ -2,7 +2,7 @@
using Artemis.Core;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.Device.Leds;
namespace Artemis.UI.Screens.Device;
public class DeviceLedsTabLedViewModel : ViewModelBase
{

View File

@ -5,10 +5,9 @@
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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.Leds.DeviceLedsTabView"
x:DataType="leds:DeviceLedsTabViewModel">
x:Class="Artemis.UI.Screens.Device.DeviceLedsTabView"
x:DataType="device:DeviceLedsTabViewModel">
<UserControl.Resources>
<converters:UriToFileNameConverter x:Key="UriToFileNameConverter" />
<converters:LedIdToStringConverter x:Key="LedIdToStringConverter" />

View File

@ -1,6 +1,7 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device.Leds;
namespace Artemis.UI.Screens.Device;
public partial class DeviceLedsTabView : ReactiveUserControl<DeviceLedsTabViewModel>
{

View File

@ -8,7 +8,7 @@ using Artemis.UI.Shared;
using DynamicData.Binding;
using ReactiveUI;
namespace Artemis.UI.Screens.Device.Leds;
namespace Artemis.UI.Screens.Device;
public class DeviceLedsTabViewModel : ActivatableViewModelBase
{

View File

@ -2,11 +2,11 @@
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:globalization="clr-namespace:System.Globalization;assembly=System.Runtime"
xmlns:layout="clr-namespace:Artemis.UI.Screens.Device.Layout"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.Layout.DeviceLogicalLayoutDialogView"
x:DataType="layout:DeviceLogicalLayoutDialogViewModel">
x:Class="Artemis.UI.Screens.Device.DeviceLogicalLayoutDialogView"
x:DataType="device:DeviceLogicalLayoutDialogViewModel">
<StackPanel>
<TextBlock TextWrapping="Wrap">Artemis couldn't automatically determine the logical layout of your</TextBlock>
<TextBlock TextWrapping="Wrap" Text="{CompiledBinding Device.RgbDevice.DeviceInfo.DeviceName, Mode=OneWay}" />
@ -27,9 +27,8 @@
<AutoCompleteBox.ItemTemplate>
<DataTemplate DataType="{x:Type globalization:RegionInfo}">
<TextBlock>
<Run Text="{CompiledBinding EnglishName}" />
<Run Text="(" /><Run FontWeight="SemiBold" Text="{CompiledBinding TwoLetterISORegionName}" />
<Run Text=")" />
<Run Text="{CompiledBinding EnglishName}"></Run>
<Run Text="(" /><Run FontWeight="SemiBold" Text="{CompiledBinding TwoLetterISORegionName}"></Run><Run Text=")" />
</TextBlock>
</DataTemplate>
</AutoCompleteBox.ItemTemplate>

View File

@ -1,17 +1,19 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
namespace Artemis.UI.Screens.Device.Layout;
namespace Artemis.UI.Screens.Device;
public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceLogicalLayoutDialogViewModel>
{
public DeviceLogicalLayoutDialogView()
{
InitializeComponent();
RegionsAutoCompleteBox.ItemFilter += SearchRegions;
Dispatcher.UIThread.InvokeAsync(DelayedAutoFocus);
}
@ -34,4 +36,5 @@ public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceL
regionInfo.NativeName.Contains(search, StringComparison.OrdinalIgnoreCase) ||
regionInfo.TwoLetterISORegionName.Contains(search, StringComparison.OrdinalIgnoreCase);
}
}

View File

@ -12,7 +12,7 @@ using PropertyChanged.SourceGenerator;
using ReactiveUI;
using ContentDialogButton = Artemis.UI.Shared.Services.Builders.ContentDialogButton;
namespace Artemis.UI.Screens.Device.Layout;
namespace Artemis.UI.Screens.Device;
public partial class DeviceLogicalLayoutDialogViewModel : ContentDialogViewModelBase
{
@ -24,7 +24,7 @@ public partial class DeviceLogicalLayoutDialogViewModel : ContentDialogViewModel
public DeviceLogicalLayoutDialogViewModel(ArtemisDevice device)
{
Device = device;
ApplyLogicalLayout = ReactiveCommand.Create(ExecuteApplyLogicalLayout, this.WhenAnyValue<DeviceLogicalLayoutDialogViewModel, RegionInfo>(vm => vm.SelectedRegion).Select(r => r != null));
ApplyLogicalLayout = ReactiveCommand.Create(ExecuteApplyLogicalLayout, this.WhenAnyValue(vm => vm.SelectedRegion).Select(r => r != null));
Regions = new ObservableCollection<RegionInfo>(CultureInfo.GetCultures(CultureTypes.SpecificCultures)
.Where(c => c.LCID != LOCALE_INVARIANT &&
c.LCID != LOCALE_NEUTRAL &&

View File

@ -2,10 +2,10 @@
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:layout="clr-namespace:Artemis.UI.Screens.Device.Layout"
xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.Layout.DevicePhysicalLayoutDialogView"
x:DataType="layout:DevicePhysicalLayoutDialogViewModel">
x:Class="Artemis.UI.Screens.Device.DevicePhysicalLayoutDialogView"
x:DataType="device:DevicePhysicalLayoutDialogViewModel">
<Grid RowDefinitions="Auto,*">
<StackPanel Grid.Row="0">
<StackPanel Orientation="Horizontal">

View File

@ -1,6 +1,7 @@
using Avalonia.ReactiveUI;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device.Layout;
namespace Artemis.UI.Screens.Device;
public partial class DevicePhysicalLayoutDialogView : ReactiveUserControl<DevicePhysicalLayoutDialogViewModel>
{
@ -8,4 +9,5 @@ public partial class DevicePhysicalLayoutDialogView : ReactiveUserControl<Device
{
InitializeComponent();
}
}

View File

@ -7,7 +7,7 @@ using Artemis.UI.Shared.Services;
using FluentAvalonia.UI.Controls;
using ReactiveUI;
namespace Artemis.UI.Screens.Device.Layout;
namespace Artemis.UI.Screens.Device;
public class DevicePhysicalLayoutDialogViewModel : ContentDialogViewModelBase
{

View File

@ -4,10 +4,9 @@
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"
x:Class="Artemis.UI.Screens.Device.InputMappings.InputMappingsTabView"
x:DataType="inputMappings:InputMappingsTabViewModel">
x:Class="Artemis.UI.Screens.Device.InputMappingsTabView"
x:DataType="device:InputMappingsTabViewModel">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<Grid RowDefinitions="Auto,Auto,*">
<StackPanel Grid.Row="0">

View File

@ -1,6 +1,7 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device.InputMappings;
namespace Artemis.UI.Screens.Device;
public partial class InputMappingsTabView : ReactiveUserControl<InputMappingsTabViewModel>
{

View File

@ -6,12 +6,13 @@ using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Exceptions;
using Artemis.UI.Shared;
using HidSharp.Reports.Units;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
using RGB.NET.Core;
using Unit = System.Reactive.Unit;
namespace Artemis.UI.Screens.Device.InputMappings;
namespace Artemis.UI.Screens.Device;
public partial class InputMappingsTabViewModel : ActivatableViewModelBase
{

View File

@ -1,24 +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:layoutProviders="clr-namespace:Artemis.UI.Screens.Device.Layout.LayoutProviders"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.Layout.LayoutProviders.CustomLayoutView"
x:DataType="layoutProviders:CustomLayoutViewModel"
ClipToBounds="False">
<StackPanel ClipToBounds="False">
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0">
<TextBlock Text="Current layout" />
<TextBlock Classes="subtitle" FontSize="12" Text="{CompiledBinding Device.LayoutSelection.Parameter, TargetNullValue=None}" TextWrapping="Wrap" />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
<Button Content="Clear" Command="{CompiledBinding ClearCustomLayout}" IsEnabled="{CompiledBinding !!Device.LayoutSelection.Parameter}" />
<!-- 5 pixels of margin between the buttons -->
<Button Margin="5,0,0,0" Content="Browse" Command="{CompiledBinding BrowseCustomLayout}" />
</StackPanel>
</Grid>
</StackPanel>
</UserControl>

View File

@ -1,11 +0,0 @@
using Avalonia.Controls;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public partial class CustomLayoutView : UserControl
{
public CustomLayoutView()
{
InitializeComponent();
}
}

View File

@ -1,80 +0,0 @@
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public class CustomLayoutViewModel : ViewModelBase, ILayoutProviderViewModel
{
private readonly CustomPathLayoutProvider _layoutProvider;
public CustomLayoutViewModel(IWindowService windowService, INotificationService notificationService, IDeviceService deviceService, CustomPathLayoutProvider layoutProvider)
{
_layoutProvider = layoutProvider;
_windowService = windowService;
_notificationService = notificationService;
_deviceService = deviceService;
}
/// <inheritdoc />
public string Name => "Custom";
/// <inheritdoc />
public string Description => "Select a layout file from a folder on your computer";
/// <inheritdoc />
public ILayoutProvider Provider => _layoutProvider;
public ArtemisDevice Device { get; set; } = null!;
private readonly IWindowService _windowService;
private readonly INotificationService _notificationService;
private readonly IDeviceService _deviceService;
public void ClearCustomLayout()
{
_layoutProvider.ConfigureDevice(Device, null);
Save();
_notificationService.CreateNotification()
.WithMessage("Cleared imported layout.")
.WithSeverity(NotificationSeverity.Informational);
}
public async Task BrowseCustomLayout()
{
string[]? files = await _windowService.CreateOpenFileDialog()
.WithTitle("Select device layout file")
.HavingFilter(f => f.WithName("Layout files").WithExtension("xml"))
.ShowAsync();
if (files?.Length > 0)
{
_layoutProvider.ConfigureDevice(Device, files[0]);
Save();
_notificationService.CreateNotification()
.WithTitle("Imported layout")
.WithMessage($"File loaded from {files[0]}")
.WithSeverity(NotificationSeverity.Informational);
}
}
/// <inheritdoc />
public void Apply()
{
_layoutProvider.ConfigureDevice(Device, null);
Save();
}
private void Save()
{
_deviceService.SaveDevice(Device);
_deviceService.LoadDeviceLayout(Device);
}
}

View File

@ -1,14 +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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.Layout.LayoutProviders.DefaultLayoutView">
<StackPanel ClipToBounds="False">
<Border Classes="card-separator" />
<StackPanel>
<TextBlock Text="Current layout" />
<TextBlock Classes="subtitle" FontSize="12" Text="Loading the default layout from the plugin or User Layouts folder." TextWrapping="Wrap" />
</StackPanel>
</StackPanel>
</UserControl>

View File

@ -1,11 +0,0 @@
using Avalonia.Controls;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public partial class DefaultLayoutView : UserControl
{
public DefaultLayoutView()
{
InitializeComponent();
}
}

View File

@ -1,42 +0,0 @@
using Artemis.Core;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public class DefaultLayoutViewModel : ViewModelBase, ILayoutProviderViewModel
{
private readonly DefaultLayoutProvider _layoutProvider;
private readonly IDeviceService _deviceService;
public DefaultLayoutViewModel(DefaultLayoutProvider layoutProvider, IDeviceService deviceService)
{
_layoutProvider = layoutProvider;
_deviceService = deviceService;
}
/// <inheritdoc />
public ILayoutProvider Provider => _layoutProvider;
public ArtemisDevice Device { get; set; } = null!;
/// <inheritdoc />
public string Name => "Default";
/// <inheritdoc />
public string Description => "Attempts to load a layout from the default paths";
/// <inheritdoc />
public void Apply()
{
_layoutProvider.ConfigureDevice(Device);
Save();
}
private void Save()
{
_deviceService.SaveDevice(Device);
_deviceService.LoadDeviceLayout(Device);
}
}

View File

@ -1,17 +0,0 @@
using Artemis.Core;
using Artemis.Core.Providers;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public interface ILayoutProviderViewModel
{
string Name { get; }
string Description { get; }
ILayoutProvider Provider { get; }
ArtemisDevice Device { get; set; }
void Apply();
}

View File

@ -1,14 +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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.Layout.LayoutProviders.NoneLayoutView">
<StackPanel ClipToBounds="False">
<Border Classes="card-separator" />
<StackPanel>
<TextBlock Text="Current layout" />
<TextBlock Classes="subtitle" FontSize="12" Text="Not loading any layout, leaving LEDs to be positioned by the device provider." TextWrapping="Wrap" />
</StackPanel>
</StackPanel>
</UserControl>

View File

@ -1,11 +0,0 @@
using Avalonia.Controls;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public partial class NoneLayoutView : UserControl
{
public NoneLayoutView()
{
InitializeComponent();
}
}

View File

@ -1,42 +0,0 @@
using Artemis.Core;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public class NoneLayoutViewModel : ViewModelBase, ILayoutProviderViewModel
{
private readonly NoneLayoutProvider _layoutProvider;
private readonly IDeviceService _deviceService;
public NoneLayoutViewModel(NoneLayoutProvider layoutProvider, IDeviceService deviceService)
{
_layoutProvider = layoutProvider;
_deviceService = deviceService;
}
/// <inheritdoc />
public ILayoutProvider Provider => _layoutProvider;
public ArtemisDevice Device { get; set; } = null!;
/// <inheritdoc />
public string Name => "None";
/// <inheritdoc />
public string Description => "Do not load any layout";
/// <inheritdoc />
public void Apply()
{
_layoutProvider.ConfigureDevice(Device);
Save();
}
private void Save()
{
_deviceService.SaveDevice(Device);
_deviceService.LoadDeviceLayout(Device);
}
}

View File

@ -1,47 +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:services="clr-namespace:Artemis.WebClient.Workshop.Services;assembly=Artemis.WebClient.Workshop"
xmlns:layoutProviders="clr-namespace:Artemis.UI.Screens.Device.Layout.LayoutProviders"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.Layout.LayoutProviders.WorkshopLayoutView"
x:DataType="layoutProviders:WorkshopLayoutViewModel">
<StackPanel ClipToBounds="False">
<Border Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Row="1" Grid.Column="0">
<TextBlock Text="Current layout" />
<TextBlock Classes="subtitle" FontSize="12" Text="Loading the layout from a workshop entry" TextWrapping="Wrap" />
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Spacing="5">
<StackPanel.Styles>
<Style Selector="ComboBox.layoutProvider /template/ ContentControl#ContentPresenter">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate x:DataType="services:InstalledEntry">
<TextBlock Text="{CompiledBinding Name}" TextWrapping="Wrap" MaxWidth="350" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Styles>
<ComboBox Classes="layoutProvider"
Width="350"
SelectedItem="{CompiledBinding SelectedEntry}"
ItemsSource="{CompiledBinding Entries}"
PlaceholderText="Select an installed layout">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="services:InstalledEntry">
<StackPanel>
<TextBlock Text="{CompiledBinding Name}" TextWrapping="Wrap" MaxWidth="350" />
<TextBlock Classes="subtitle" Text="{CompiledBinding Author}" TextWrapping="Wrap" MaxWidth="350" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button HorizontalAlignment="Right" Click="Button_OnClick">Browse workshop layouts</Button>
</StackPanel>
</Grid>
</StackPanel>
</UserControl>

View File

@ -1,20 +0,0 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public partial class WorkshopLayoutView : ReactiveUserControl<WorkshopLayoutViewModel>
{
public WorkshopLayoutView()
{
InitializeComponent();
}
private async void Button_OnClick(object? sender, RoutedEventArgs e)
{
if (ViewModel != null && await ViewModel.BrowseLayouts())
(VisualRoot as Window)?.Close();
}
}

View File

@ -1,83 +0,0 @@
using System.Collections.ObjectModel;
using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Providers;
using Artemis.WebClient.Workshop.Services;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
public partial class WorkshopLayoutViewModel : ActivatableViewModelBase, ILayoutProviderViewModel
{
[Notify] private InstalledEntry? _selectedEntry;
private readonly WorkshopLayoutProvider _layoutProvider;
private readonly IDeviceService _deviceService;
private readonly IWindowService _windowService;
private readonly IRouter _router;
public WorkshopLayoutViewModel(WorkshopLayoutProvider layoutProvider, IWorkshopService workshopService, IDeviceService deviceService, IWindowService windowService, IRouter router)
{
_layoutProvider = layoutProvider;
_deviceService = deviceService;
_windowService = windowService;
_router = router;
Entries = new ObservableCollection<InstalledEntry>(workshopService.GetInstalledEntries().Where(e => e.EntryType == EntryType.Layout));
this.WhenAnyValue(vm => vm.SelectedEntry).Subscribe(ApplyEntry);
this.WhenActivated((CompositeDisposable _) => SelectedEntry = Entries.FirstOrDefault(e => e.EntryId.ToString() == Device.LayoutSelection.Parameter));
}
/// <inheritdoc />
public ILayoutProvider Provider => _layoutProvider;
public ArtemisDevice Device { get; set; } = null!;
public ObservableCollection<InstalledEntry> Entries { get; }
/// <inheritdoc />
public string Name => "Workshop";
/// <inheritdoc />
public string Description => "Load a layout from the workshop";
/// <inheritdoc />
public void Apply()
{
_layoutProvider.ConfigureDevice(Device, null);
Save();
}
public async Task<bool> BrowseLayouts()
{
if (!await _windowService.ShowConfirmContentDialog("Open workshop", "Do you want to close this window and view the workshop?"))
return false;
await _router.Navigate("workshop/entries/layouts/1");
return true;
}
private void ApplyEntry(InstalledEntry? entry)
{
if (entry == null || Device.LayoutSelection.Parameter == entry.EntryId.ToString())
return;
_layoutProvider.ConfigureDevice(Device, entry);
Save();
}
private void Save()
{
_deviceService.SaveDevice(Device);
_deviceService.LoadDeviceLayout(Device);
}
}

View File

@ -1,27 +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:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryImageView"
x:DataType="details:EntryImageViewModel">
<Border Classes="card" Padding="0">
<Grid RowDefinitions="Auto,*">
<Border Grid.Row="0" ClipToBounds="True" CornerRadius="4 4 0 0" Padding="0">
<Image asyncImageLoader:ImageLoader.Source="{CompiledBinding ThumbnailUrl}" Stretch="Uniform" HorizontalAlignment="Stretch" MaxHeight="250" />
</Border>
<Border Grid.Row="1" ClipToBounds="True" CornerRadius="0 0 4 4" Background="{DynamicResource ControlFillColorDefaultBrush}">
<StackPanel Margin="16">
<TextBlock Text="{CompiledBinding Image.Name}" TextTrimming="CharacterEllipsis" />
<TextBlock Classes="subtitle"
Text="{CompiledBinding Image.Description}"
TextWrapping="Wrap"
IsVisible="{CompiledBinding Image.Description, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
</StackPanel>
</Border>
</Grid>
</Border>
</UserControl>

View File

@ -1,13 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Workshop.Entries.Details;
public partial class EntryImageView : UserControl
{
public EntryImageView()
{
InitializeComponent();
}
}

View File

@ -1,18 +0,0 @@
using Artemis.UI.Shared;
using Artemis.WebClient.Workshop;
namespace Artemis.UI.Screens.Workshop.Entries.Details;
public class EntryImageViewModel : ViewModelBase
{
public EntryImageViewModel(IImage image)
{
Image = image;
Url = $"{WorkshopConstants.WORKSHOP_URL}/images/{image.Id}.png";
ThumbnailUrl = $"{WorkshopConstants.WORKSHOP_URL}/images/{image.Id}-thumb.png";
}
public IImage Image { get; }
public string Url { get; }
public string ThumbnailUrl { get; }
}

View File

@ -1,12 +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:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryImagesDialogView"
x:DataType="details:EntryImagesDialogViewModel"
Margin="-25 -63 -25 -25">
<Image asyncImageLoader:ImageLoader.Source="{CompiledBinding CurrentImage.Url}" Stretch="Uniform"/>
</UserControl>

View File

@ -1,14 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Entries.Details;
public partial class EntryImagesDialogView : ReactiveUserControl<EntryImagesDialogViewModel>
{
public EntryImagesDialogView()
{
InitializeComponent();
}
}

View File

@ -1,61 +0,0 @@
using System;
using System.Collections.ObjectModel;
using System.Reactive;
using System.Reactive.Disposables;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using FluentAvalonia.UI.Controls;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Entries.Details;
public partial class EntryImagesDialogViewModel : ContentDialogViewModelBase
{
[Notify] private EntryImageViewModel _currentImage;
private readonly IInputService _inputService;
public EntryImagesDialogViewModel(ObservableCollection<EntryImageViewModel> images, EntryImageViewModel startImage, IInputService inputService)
{
_currentImage = startImage;
_inputService = inputService;
Images = images;
Previous = ReactiveCommand.Create(() => CurrentImage = Images[(Images.IndexOf(CurrentImage) - 1 + Images.Count) % Images.Count]);
Next = ReactiveCommand.Create(() => CurrentImage = Images[(Images.IndexOf(CurrentImage) + 1) % Images.Count]);
this.WhenActivated(d =>
{
if (ContentDialog == null)
return;
_inputService.KeyboardKeyDown += InputServiceOnKeyboardKeyDown;
ContentDialog.Closing += ContentDialogOnClosing;
Disposable.Create(() =>
{
_inputService.KeyboardKeyDown -= InputServiceOnKeyboardKeyDown;
ContentDialog.Closing -= ContentDialogOnClosing;
}).DisposeWith(d);
});
}
private void InputServiceOnKeyboardKeyDown(object? sender, ArtemisKeyboardKeyEventArgs e)
{
// Leveraging InputService to avoid issues with which control has focus
if (e.Key == KeyboardKey.ArrowRight)
Next.Execute().Subscribe();
else if (e.Key == KeyboardKey.ArrowLeft)
Previous.Execute().Subscribe();
else if (e.Key == KeyboardKey.Escape)
ContentDialog?.Hide(ContentDialogResult.None);
}
private void ContentDialogOnClosing(ContentDialog sender, ContentDialogClosingEventArgs args)
{
args.Cancel = args.Result != ContentDialogResult.None;
}
public ObservableCollection<EntryImageViewModel> Images { get; }
public ReactiveCommand<Unit, EntryImageViewModel> Previous { get; }
public ReactiveCommand<Unit, EntryImageViewModel> Next { get; }
}

View File

@ -1,33 +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:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryImagesView"
x:DataType="details:EntryImagesViewModel">
<ScrollViewer Classes="with-padding" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{CompiledBinding Images}">
<ItemsControl.Styles>
<Styles>
<Style Selector="ItemsControl > ContentPresenter">
<Setter Property="Margin" Value="0 0 0 10"></Setter>
</Style>
<Style Selector="ItemsControl > ContentPresenter:nth-last-child(1)">
<Setter Property="Margin" Value="0 0 0 0"></Setter>
</Style>
</Styles>
</ItemsControl.Styles>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{CompiledBinding}" Cursor="Hand" PointerPressed="InputElement_OnPointerPressed"></ContentPresenter>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</UserControl>

View File

@ -1,23 +0,0 @@
using Avalonia;
using Avalonia.Input;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Entries.Details;
public partial class EntryImagesView : ReactiveUserControl<EntryImagesViewModel>
{
public EntryImagesView()
{
InitializeComponent();
}
private void InputElement_OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (sender is not IDataContextProvider contextProvider)
return;
if (contextProvider.DataContext is not EntryImageViewModel entryImageViewModel)
return;
ViewModel?.ShowImages(entryImageViewModel);
}
}

View File

@ -1,30 +0,0 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
namespace Artemis.UI.Screens.Workshop.Entries.Details;
public class EntryImagesViewModel : ViewModelBase
{
private readonly IWindowService _windowService;
public ObservableCollection<EntryImageViewModel> Images { get; }
public EntryImagesViewModel(IEntryDetails entryDetails, IWindowService windowService)
{
_windowService = windowService;
Images = new ObservableCollection<EntryImageViewModel>(entryDetails.Images.Select(i => new EntryImageViewModel(i)));
}
public async Task ShowImages(EntryImageViewModel image)
{
await _windowService.CreateContentDialog()
.WithViewModel(out EntryImagesDialogViewModel vm, Images, image)
.HavingPrimaryButton(b => b.WithText("Previous").WithCommand(vm.Previous))
.HavingSecondaryButton(b => b.WithText("Next").WithCommand(vm.Next))
.WithFullScreen()
.ShowAsync();
}
}

View File

@ -1,83 +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:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryInfoView"
x:DataType="details:EntryInfoViewModel">
<UserControl.Resources>
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
<converters:DateTimeConverter x:Key="DateTimeConverter" />
</UserControl.Resources>
<StackPanel>
<Panel>
<Border CornerRadius="6"
HorizontalAlignment="Left"
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>
<Button Classes="icon-button"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Command="{CompiledBinding CopyShareLink}"
ToolTip.Tip="Copy share link">
<avalonia:MaterialIcon Kind="ShareVariant" />
</Button>
</Panel>
<TextBlock Theme="{StaticResource TitleTextBlockStyle}"
MaxLines="3"
TextTrimming="CharacterEllipsis"
Text="{CompiledBinding Entry.Name, FallbackValue=Title }" />
<TextBlock Classes="subtitle" TextTrimming="CharacterEllipsis" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
<TextBlock Margin="0 8" TextWrapping="Wrap" Text="{CompiledBinding Entry.Summary, FallbackValue=Summary}" />
<!-- Categories -->
<ItemsControl ItemsSource="{CompiledBinding Entry.Categories}" Margin="0 0 -8 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0 0 8 0">
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Margin="0 0 3 0"></avalonia:MaterialIcon>
<TextBlock Text="{CompiledBinding Name}" TextTrimming="CharacterEllipsis" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Border Classes="card-separator"></Border>
<TextBlock Margin="0 0 0 8">
<avalonia:MaterialIcon Kind="Downloads" />
<Run Classes="h5" Text="{CompiledBinding Entry.Downloads, FallbackValue=0}" />
<Run>downloads</Run>
</TextBlock>
<TextBlock Classes="subtitle"
ToolTip.Tip="{CompiledBinding Entry.CreatedAt, Converter={StaticResource DateTimeConverter}}">
<avalonia:MaterialIcon Kind="Calendar" />
<Run>Created</Run>
<Run Text="{CompiledBinding Entry.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
</TextBlock>
<TextBlock Classes="subtitle"
ToolTip.Tip="{CompiledBinding UpdatedAt, Converter={StaticResource DateTimeConverter}}">
<avalonia:MaterialIcon Kind="Update" />
<Run>Updated</Run>
<Run Text="{CompiledBinding UpdatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
</TextBlock>
</StackPanel>
</UserControl>

View File

@ -1,13 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Workshop.Entries.Details;
public partial class EntryInfoView : UserControl
{
public EntryInfoView()
{
InitializeComponent();
}
}

View File

@ -1,28 +0,0 @@
using System;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
namespace Artemis.UI.Screens.Workshop.Entries.Details;
public class EntryInfoViewModel : ViewModelBase
{
private readonly INotificationService _notificationService;
public IGetEntryById_Entry Entry { get; }
public DateTimeOffset? UpdatedAt { get; }
public EntryInfoViewModel(IGetEntryById_Entry entry, INotificationService notificationService)
{
_notificationService = notificationService;
Entry = entry;
UpdatedAt = Entry.LatestRelease?.CreatedAt ?? Entry.CreatedAt;
}
public async Task CopyShareLink()
{
await Shared.UI.Clipboard.SetTextAsync($"{WorkshopConstants.WORKSHOP_URL}/entries/{Entry.Id}/{StringUtilities.UrlFriendly(Entry.Name)}");
_notificationService.CreateNotification().WithTitle("Copied share link to clipboard.").Show();
}
}

View File

@ -1,52 +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:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryReleasesView"
x:DataType="details:EntryReleasesViewModel">
<UserControl.Resources>
<converters:DateTimeConverter x:Key="DateTimeConverter" />
<sharedConverters:BytesToStringConverter x:Key="BytesToStringConverter" />
</UserControl.Resources>
<StackPanel>
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Latest release</TextBlock>
<Border Classes="card-separator" />
<Button HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Command="{CompiledBinding DownloadLatestRelease}">
<Grid ColumnDefinitions="Auto,*">
<!-- Icon -->
<Border Grid.Column="0"
CornerRadius="4"
Background="{StaticResource SystemAccentColor}"
VerticalAlignment="Center"
Margin="0 6"
Width="50"
Height="50"
ClipToBounds="True">
<avalonia:MaterialIcon Kind="Download"></avalonia:MaterialIcon>
</Border>
<!-- Body -->
<StackPanel Grid.Column="1" Margin="10 0" VerticalAlignment="Center">
<TextBlock Text="{CompiledBinding Entry.LatestRelease.Version, FallbackValue=Version}"></TextBlock>
<TextBlock Classes="subtitle">
<avalonia:MaterialIcon Kind="BoxOutline" />
<Run Text="{CompiledBinding Entry.LatestRelease.DownloadSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay}"></Run>
</TextBlock>
<TextBlock Classes="subtitle"
ToolTip.Tip="{CompiledBinding Entry.LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}}">
<avalonia:MaterialIcon Kind="Calendar" />
<Run>Created</Run>
<Run Text="{CompiledBinding Entry.LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
</TextBlock>
</StackPanel>
</Grid>
</Button>
</StackPanel>
</UserControl>

View File

@ -1,13 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Workshop.Entries.Details;
public partial class EntryReleasesView : UserControl
{
public EntryReleasesView()
{
InitializeComponent();
}
}

View File

@ -1,66 +0,0 @@
using System;
using System.Reactive;
using System.Threading;
using System.Threading.Tasks;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Utilities;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
using Artemis.WebClient.Workshop.Services;
using Humanizer;
using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Entries.Details;
public class EntryReleasesViewModel : ViewModelBase
{
private readonly EntryInstallationHandlerFactory _factory;
private readonly IWindowService _windowService;
private readonly INotificationService _notificationService;
public EntryReleasesViewModel(IGetEntryById_Entry entry, EntryInstallationHandlerFactory factory, IWindowService windowService, INotificationService notificationService)
{
_factory = factory;
_windowService = windowService;
_notificationService = notificationService;
Entry = entry;
DownloadLatestRelease = ReactiveCommand.CreateFromTask(ExecuteDownloadLatestRelease);
}
public IGetEntryById_Entry Entry { get; }
public ReactiveCommand<Unit, Unit> DownloadLatestRelease { get; }
public Func<InstalledEntry, Task>? OnInstallationFinished { get; set; }
private async Task ExecuteDownloadLatestRelease(CancellationToken cancellationToken)
{
if (Entry.LatestRelease == null)
return;
bool confirm = await _windowService.ShowConfirmContentDialog(
"Install latest release",
$"Are you sure you want to download and install version {Entry.LatestRelease.Version} of {Entry.Name}?"
);
if (!confirm)
return;
IEntryInstallationHandler installationHandler = _factory.CreateHandler(Entry.EntryType);
EntryInstallResult result = await installationHandler.InstallAsync(Entry, Entry.LatestRelease, new Progress<StreamProgress>(), cancellationToken);
if (result.IsSuccess && result.Entry != null)
{
if (OnInstallationFinished != null)
await OnInstallationFinished(result.Entry);
_notificationService.CreateNotification().WithTitle($"{Entry.EntryType.Humanize(LetterCasing.Sentence)} installed").WithSeverity(NotificationSeverity.Success).Show();
}
else
{
_notificationService.CreateNotification()
.WithTitle($"Failed to install {Entry.EntryType.Humanize(LetterCasing.LowerCase)}")
.WithMessage(result.Message)
.WithSeverity(NotificationSeverity.Error).Show();
}
}
}

View File

@ -23,7 +23,7 @@
</Styles>
</controls:NavigationView.Styles>
<controls:Frame Name="TabFrame" IsNavigationStackEnabled="False" CacheSize="0" Padding="20 20 10 20">
<controls:Frame Name="TabFrame" IsNavigationStackEnabled="False" CacheSize="0" Padding="20">
<controls:Frame.NavigationPageFactory>
<ui:PageFactory/>
</controls:Frame.NavigationPageFactory>

View File

@ -0,0 +1,8 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Entries.EntryInstallationDialogView">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,13 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Workshop.Entries;
public partial class EntryInstallationDialogView : UserControl
{
public EntryInstallationDialogView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,8 @@
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.Workshop.Entries;
public class EntryInstallationDialogViewModel : ContentDialogViewModelBase
{
}

View File

@ -4,10 +4,9 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:entries="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:list="clr-namespace:Artemis.UI.Screens.Workshop.Entries.List"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Entries.List.EntryListInputView"
x:DataType="list:EntryListInputViewModel">
x:Class="Artemis.UI.Screens.Workshop.Entries.EntryListInputView"
x:DataType="entries:EntryListInputViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MaxWidth="500" />

View File

@ -1,6 +1,8 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Workshop.Entries.List;
namespace Artemis.UI.Screens.Workshop.Entries;
public partial class EntryListInputView : UserControl
{

View File

@ -4,7 +4,7 @@ using Artemis.UI.Shared;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Entries.List;
namespace Artemis.UI.Screens.Workshop.Entries;
public partial class EntryListInputViewModel : ViewModelBase
{

View File

@ -6,10 +6,9 @@
xmlns:entries1="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:list="clr-namespace:Artemis.UI.Screens.Workshop.Entries.List"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="110"
x:Class="Artemis.UI.Screens.Workshop.Entries.List.EntryListItemView"
x:DataType="list:EntryListItemViewModel">
x:Class="Artemis.UI.Screens.Workshop.Entries.EntryListItemView"
x:DataType="entries1:EntryListItemViewModel">
<UserControl.Resources>
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
<converters:DateTimeConverter x:Key="DateTimeConverter" />

View File

@ -1,6 +1,6 @@
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Entries.List;
namespace Artemis.UI.Screens.Workshop.Entries;
public partial class EntryListItemView : ReactiveUserControl<EntryListItemViewModel>
{

View File

@ -1,12 +1,17 @@
using System;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Services;
using Avalonia.Media.Imaging;
using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Entries.List;
namespace Artemis.UI.Screens.Workshop.Entries;
public class EntryListItemViewModel : ActivatableViewModelBase
{

View File

@ -17,7 +17,7 @@ using PropertyChanged.SourceGenerator;
using ReactiveUI;
using StrawberryShake;
namespace Artemis.UI.Screens.Workshop.Entries.List;
namespace Artemis.UI.Screens.Workshop.Entries;
public abstract partial class EntryListViewModel : RoutableScreen<WorkshopListParameters>
{
@ -42,8 +42,8 @@ public abstract partial class EntryListViewModel : RoutableScreen<WorkshopListPa
_route = route;
_workshopClient = workshopClient;
_notificationService = notificationService;
_showPagination = this.WhenAnyValue<EntryListViewModel, int>(vm => vm.TotalPages).Select(t => t > 1).ToProperty(this, vm => vm.ShowPagination);
_isLoading = this.WhenAnyValue<EntryListViewModel, bool, int, int>(vm => vm.Page, vm => vm.LoadedPage, (p, c) => p != c).ToProperty(this, vm => vm.IsLoading);
_showPagination = this.WhenAnyValue(vm => vm.TotalPages).Select(t => t > 1).ToProperty(this, vm => vm.ShowPagination);
_isLoading = this.WhenAnyValue(vm => vm.Page, vm => vm.LoadedPage, (p, c) => p != c).ToProperty(this, vm => vm.IsLoading);
CategoriesViewModel = categoriesViewModel;
InputViewModel = entryListInputViewModel;
@ -56,7 +56,7 @@ public abstract partial class EntryListViewModel : RoutableScreen<WorkshopListPa
Entries = entries;
// Respond to page changes
this.WhenAnyValue<EntryListViewModel, int>(vm => vm.Page).Skip(1).Subscribe(p => Task.Run(() => router.Navigate($"{_route}/{p}")));
this.WhenAnyValue(vm => vm.Page).Skip(1).Subscribe(p => Task.Run(() => router.Navigate($"{_route}/{p}")));
this.WhenActivated(d =>
{

View File

@ -9,10 +9,9 @@
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntrySpecificationsView"
x:DataType="details:EntrySpecificationsViewModel">
x:Class="Artemis.UI.Screens.Workshop.Entries.EntrySpecificationsView"
x:DataType="entries:EntrySpecificationsViewModel">
<Grid RowDefinitions="Auto,Auto,*,Auto">
<StackPanel>
<StackPanel.Styles>

View File

@ -1,4 +1,5 @@
using System.Linq;
using Artemis.UI.Shared.Extensions;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
@ -7,9 +8,8 @@ using Avalonia.ReactiveUI;
using AvaloniaEdit.TextMate;
using ReactiveUI;
using TextMateSharp.Grammars;
using VisualExtensions = Artemis.UI.Shared.Extensions.VisualExtensions;
namespace Artemis.UI.Screens.Workshop.Entries.Details;
namespace Artemis.UI.Screens.Workshop.Entries;
public partial class EntrySpecificationsView : ReactiveUserControl<EntrySpecificationsViewModel>
{
@ -23,7 +23,7 @@ public partial class EntrySpecificationsView : ReactiveUserControl<EntrySpecific
DescriptionEditor.Options.AllowScrollBelowDocument = false;
RegistryOptions options = new(ThemeName.Dark);
TextMate.Installation? install = TextMate.InstallTextMate(DescriptionEditor, options);
TextMate.Installation? install = DescriptionEditor.InstallTextMate(options);
install.SetGrammar(options.GetScopeByExtension(".md"));
@ -45,8 +45,8 @@ public partial class EntrySpecificationsView : ReactiveUserControl<EntrySpecific
if (_previewScrollViewer != null)
_previewScrollViewer.PropertyChanged -= PreviewScrollViewerOnPropertyChanged;
_editorScrollViewer = VisualExtensions.GetVisualChildrenOfType<ScrollViewer>(DescriptionEditor).FirstOrDefault();
_previewScrollViewer = VisualExtensions.GetVisualChildrenOfType<ScrollViewer>(DescriptionPreview).FirstOrDefault();
_editorScrollViewer = DescriptionEditor.GetVisualChildrenOfType<ScrollViewer>().FirstOrDefault();
_previewScrollViewer = DescriptionPreview.GetVisualChildrenOfType<ScrollViewer>().FirstOrDefault();
if (_editorScrollViewer != null)
_editorScrollViewer.PropertyChanged += EditorScrollViewerOnPropertyChanged;

View File

@ -22,7 +22,7 @@ using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Helpers;
using StrawberryShake;
namespace Artemis.UI.Screens.Workshop.Entries.Details;
namespace Artemis.UI.Screens.Workshop.Entries;
public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
{
@ -52,12 +52,12 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
.Subscribe();
SelectedCategories = selectedCategories;
this.ValidationRule<EntrySpecificationsViewModel, string>(vm => vm.Name, s => !string.IsNullOrWhiteSpace(s), "Name is required");
this.ValidationRule<EntrySpecificationsViewModel, string>(vm => vm.Summary, s => !string.IsNullOrWhiteSpace(s), "Summary is required");
ValidationHelper descriptionRule = this.ValidationRule<EntrySpecificationsViewModel, string>(vm => vm.Description, s => !string.IsNullOrWhiteSpace(s), "Description is required");
this.ValidationRule(vm => vm.Name, s => !string.IsNullOrWhiteSpace(s), "Name is required");
this.ValidationRule(vm => vm.Summary, s => !string.IsNullOrWhiteSpace(s), "Summary is required");
ValidationHelper descriptionRule = this.ValidationRule(vm => vm.Description, s => !string.IsNullOrWhiteSpace(s), "Description is required");
// These don't use inputs that support validation messages, do so manually
ValidationHelper iconRule = this.ValidationRule<EntrySpecificationsViewModel, Bitmap>(vm => vm.IconBitmap, s => s != null, "Icon required");
ValidationHelper iconRule = this.ValidationRule(vm => vm.IconBitmap, s => s != null, "Icon required");
ValidationHelper categoriesRule = this.ValidationRule(vm => vm.Categories, Categories.ToObservableChangeSet().AutoRefresh(c => c.IsSelected).Filter(c => c.IsSelected).IsNotEmpty(),
"At least one category must be selected"
);

View File

@ -1,18 +1,17 @@
using System;
using Artemis.UI.Screens.Workshop.Categories;
using Artemis.UI.Screens.Workshop.Entries.List;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
namespace Artemis.UI.Screens.Workshop.Entries.Tabs;
public class LayoutListViewModel : List.EntryListViewModel
public class LayoutListViewModel : EntryListViewModel
{
public LayoutListViewModel(IWorkshopClient workshopClient,
IRouter router,
CategoriesViewModel categoriesViewModel,
List.EntryListInputViewModel entryListInputViewModel,
EntryListInputViewModel entryListInputViewModel,
INotificationService notificationService,
Func<IGetEntries_Entries_Items, EntryListItemViewModel> getEntryListViewModel)
: base("workshop/entries/layout", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)

View File

@ -1,18 +1,17 @@
using System;
using Artemis.UI.Screens.Workshop.Categories;
using Artemis.UI.Screens.Workshop.Entries.List;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
namespace Artemis.UI.Screens.Workshop.Entries.Tabs;
public class ProfileListViewModel : List.EntryListViewModel
public class ProfileListViewModel : EntryListViewModel
{
public ProfileListViewModel(IWorkshopClient workshopClient,
IRouter router,
CategoriesViewModel categoriesViewModel,
List.EntryListInputViewModel entryListInputViewModel,
EntryListInputViewModel entryListInputViewModel,
INotificationService notificationService,
Func<IGetEntries_Entries_Items, EntryListItemViewModel> getEntryListViewModel)
: base("workshop/entries/profiles", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)

View File

@ -1,15 +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:image="clr-namespace:Artemis.UI.Screens.Workshop.Image"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Image.ImagePropertiesDialogView"
x:DataType="image:ImagePropertiesDialogViewModel">
<StackPanel Width="400">
<Label>Name</Label>
<TextBox Text="{CompiledBinding Name}" Watermark="Name" MaxLength="50"/>
<Label>Description</Label>
<TextBox AcceptsReturn="True" Height="150" Text="{CompiledBinding Description}" Watermark="Description" MaxLength="150" MaxLines="7"/>
</StackPanel>
</UserControl>

Some files were not shown because too many files have changed in this diff Show More