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

Add AXAML hot reload (debug only)

Major progress on default entries too I guess lmao
This commit is contained in:
Robert 2025-11-15 15:07:54 +01:00
parent 20fb6b7662
commit b351f685f7
24 changed files with 963 additions and 682 deletions

View File

@ -141,6 +141,11 @@ public static class Constants
/// </summary>
public static ReadOnlyCollection<string> StartupArguments { get; set; } = null!;
public static string? GetStartupRoute()
{
return StartupArguments.FirstOrDefault(a => a.StartsWith("--route=artemis://"))?.Split("--route=artemis://")[1];
}
internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")};
internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};
internal static readonly JsonSerializerOptions JsonConvertSettings = new() {Converters = {new SKColorConverter(), new NumericJsonConverter()}};

View File

@ -251,8 +251,14 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
if (_previousWindowRoute != null && _currentRouteSubject.Value == "blank")
Dispatcher.UIThread.InvokeAsync(async () => await Navigate(_previousWindowRoute, new RouterNavigationOptions {AddToHistory = false, EnableLogging = false}));
else if (_currentRouteSubject.Value == null || _currentRouteSubject.Value == "blank")
{
string? startupRoute = Constants.GetStartupRoute();
if (startupRoute != null)
Dispatcher.UIThread.InvokeAsync(async () => await Navigate(startupRoute, new RouterNavigationOptions {AddToHistory = false, EnableLogging = true}));
else
Dispatcher.UIThread.InvokeAsync(async () => await Navigate("home", new RouterNavigationOptions {AddToHistory = false, EnableLogging = true}));
}
}
private void MainWindowServiceOnMainWindowClosed(object? sender, EventArgs e)
{

View File

@ -16,6 +16,7 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using DryIoc;
using HotAvalonia;
using ReactiveUI;
namespace Artemis.UI.Windows;
@ -27,6 +28,8 @@ public class App : Application
public override void Initialize()
{
this.EnableHotReload();
// If Artemis is already running, bring it to foreground and stop this process
if (FocusExistingInstance())
{
@ -89,7 +92,7 @@ public class App : Application
try
{
CancellationTokenSource cts = new();
cts.CancelAfter(2000);
cts.CancelAfter(5000);
HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground") {Content = new StringContent(route ?? "")}, cts.Token);
httpResponseMessage.EnsureSuccessStatusCode();

View File

@ -35,40 +35,4 @@
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<Compile Update="Screens\Workshop\Plugin\Dialogs\DeviceProviderPickerDialogView.axaml.cs">
<DependentUpon>DeviceProviderPickerDialogView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Screens\Workshop\Plugin\Dialogs\DeviceSelectionDialogView.axaml.cs">
<DependentUpon>DeviceSelectionDialogView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Screens\Workshop\Layout\LayoutListView.axaml.cs">
<DependentUpon>LayoutListView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Screens\Workshop\Plugins\PluginListView.axaml.cs">
<DependentUpon>LayoutListView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Screens\Workshop\Profile\ProfileListView.axaml.cs">
<DependentUpon>LayoutListView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Screens\Workshop\EntryReleases\EntryReleasesView.axaml.cs">
<DependentUpon>EntryReleasesView.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<UpToDateCheckInput Remove="Screens\Workshop\Entries\Tabs\PluginListView.axaml" />
<UpToDateCheckInput Remove="Screens\Workshop\Entries\Tabs\ProfileListView.axaml" />
<UpToDateCheckInput Remove="Screens\Workshop\Plugins\Dialogs\PluginDialogView.axaml" />
<UpToDateCheckInput Remove="Screens\Scripting\Dialogs\ScriptConfigurationCreateView.axaml" />
<UpToDateCheckInput Remove="Screens\Scripting\Dialogs\ScriptConfigurationEditView.axaml" />
<UpToDateCheckInput Remove="Screens\Scripting\ScriptsDialogView.axaml" />
</ItemGroup>
</Project>

View File

@ -22,7 +22,7 @@
Foreground="White"
FontSize="32"
Margin="30"
Text=" Welcome to Artemis, the unified RGB platform.">
Text="Welcome to Artemis, the unified RGB platform.">
<TextBlock.Effect>
<DropShadowEffect Color="Black" OffsetX="2" OffsetY="2" BlurRadius="5"></DropShadowEffect>
</TextBlock.Effect>

View File

@ -7,26 +7,52 @@
x:Class="Artemis.UI.Screens.StartupWizard.Steps.DefaultEntriesStepView"
x:DataType="steps:DefaultEntriesStepViewModel">
<Border Classes="card">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,*">
<StackPanel Grid.Row="0">
<Grid RowDefinitions="*,Auto">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<StackPanel>
<TextBlock TextWrapping="Wrap">
Installing default plugins and profiles, these will provide you with a basic setup to get started with Artemis.
</TextBlock>
</StackPanel>
<TextBlock Grid.Row="2">
<Run Text="Installed"/>
<Run Text="{CompiledBinding InstalledEntries}"/>
<Run Text=" of "/>
<Run Text="{CompiledBinding TotalEntries}"/>
<Run Text=" entries."/>
Below is a list of default features that can be installed to get you started with Artemis. You can always install or uninstall features later via the Workshop.
</TextBlock>
<ProgressBar Grid.Row="3" Minimum="0" Maximum="{CompiledBinding TotalEntries}" IsIndeterminate="{CompiledBinding FetchingDefaultEntries}" Value="{CompiledBinding InstalledEntries}" />
<StackPanel Grid.Row="4" Orientation="Vertical" IsVisible="{CompiledBinding CurrentEntry, Converter={x:Static ObjectConverters.IsNotNull}}" Margin="0 10">
<TextBlock Text="{CompiledBinding CurrentEntry.Name, StringFormat='Currently installing: {0}'}" />
<ProgressBar Minimum="0" Maximum="100" Value="{CompiledBinding InstallProgress}" />
<TextBlock Classes="card-title" IsVisible="{CompiledBinding DeviceProviderEntryViewModels.Count}">
Device providers
</TextBlock>
<ItemsControl ItemsSource="{CompiledBinding DeviceProviderEntryViewModels}" Margin="0,0,5,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2" ColumnSpacing="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBlock Classes="card-title" IsVisible="{CompiledBinding EssentialEntryViewModels.Count}">
Essentials
</TextBlock>
<ItemsControl ItemsSource="{CompiledBinding EssentialEntryViewModels}" Margin="0,0,5,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2" ColumnSpacing="5"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBlock Classes="card-title" IsVisible="{CompiledBinding OtherEntryViewModels.Count}">
Other features
</TextBlock>
<ItemsControl ItemsSource="{CompiledBinding OtherEntryViewModels}" Margin="0,0,5,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="2" ColumnSpacing="5"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</ScrollViewer>
<ProgressBar Grid.Row="1"
Minimum="0"
Maximum="{CompiledBinding TotalEntries}"
IsIndeterminate="{CompiledBinding FetchingDefaultEntries}"
Value="{CompiledBinding CurrentEntries}" />
</Grid>
</Border>
</UserControl>

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -8,7 +9,6 @@ using Artemis.UI.Extensions;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Utilities;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
using Artemis.WebClient.Workshop.Services;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
@ -20,25 +20,31 @@ public partial class DefaultEntriesStepViewModel : WizardStepViewModel
{
[Notify] private bool _workshopReachable;
[Notify] private bool _fetchingDefaultEntries;
[Notify] private int _totalEntries;
[Notify] private int _installedEntries;
[Notify] private int _installProgress;
[Notify] private IEntrySummary? _currentEntry;
[Notify] private bool _installed;
[Notify] private int _currentEntries;
[Notify] private int _totalEntries = 1;
private readonly IWorkshopService _workshopService;
private readonly IWorkshopClient _client;
private readonly IWindowService _windowService;
private readonly Progress<StreamProgress> _currentEntryProgress = new();
private readonly Func<IEntrySummary, DefaultEntryItemViewModel> _getDefaultEntryItemViewModel;
public ObservableCollection<DefaultEntryItemViewModel> DeviceProviderEntryViewModels { get; } = [];
public ObservableCollection<DefaultEntryItemViewModel> EssentialEntryViewModels { get; } = [];
public ObservableCollection<DefaultEntryItemViewModel> OtherEntryViewModels { get; } = [];
public DefaultEntriesStepViewModel(IWorkshopService workshopService, IDeviceService deviceService, IWorkshopClient client, IWindowService windowService)
public DefaultEntriesStepViewModel(IWorkshopService workshopService, IDeviceService deviceService, IWorkshopClient client,
Func<IEntrySummary, DefaultEntryItemViewModel> getDefaultEntryItemViewModel)
{
_workshopService = workshopService;
_client = client;
_windowService = windowService;
_currentEntryProgress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage;
_getDefaultEntryItemViewModel = getDefaultEntryItemViewModel;
Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen<SettingsStepViewModel>());
ContinueText = "Install selected entries";
Continue = ReactiveCommand.CreateFromTask(async ct =>
{
if (Installed)
Wizard.ChangeScreen<SettingsStepViewModel>();
else
await Install(ct);
});
GoBack = ReactiveCommand.Create(() =>
{
if (deviceService.EnabledDevices.Count == 0)
@ -49,22 +55,57 @@ public partial class DefaultEntriesStepViewModel : WizardStepViewModel
this.WhenActivatedAsync(async d =>
{
WorkshopReachable = await _workshopService.ValidateWorkshopStatus(d.AsCancellationToken());
WorkshopReachable = await workshopService.ValidateWorkshopStatus(d.AsCancellationToken());
if (WorkshopReachable)
{
await InstallDefaultEntries(d.AsCancellationToken());
await GetDefaultEntries(d.AsCancellationToken());
}
});
}
public async Task<bool> InstallDefaultEntries(CancellationToken cancellationToken)
private async Task Install(CancellationToken cancellationToken)
{
FetchingDefaultEntries = true;
TotalEntries = 0;
InstalledEntries = 0;
// Remove entries that aren't to be installed
RemoveUnselectedEntries(DeviceProviderEntryViewModels);
RemoveUnselectedEntries(EssentialEntryViewModels);
RemoveUnselectedEntries(OtherEntryViewModels);
TotalEntries = DeviceProviderEntryViewModels.Count + EssentialEntryViewModels.Count + OtherEntryViewModels.Count;
CurrentEntries = 0;
// Install entries one by one, removing them from the list as we go
List<DefaultEntryItemViewModel> entries = [..DeviceProviderEntryViewModels, ..EssentialEntryViewModels, ..OtherEntryViewModels];
foreach (DefaultEntryItemViewModel defaultEntryItemViewModel in entries)
{
cancellationToken.ThrowIfCancellationRequested();
bool removeFromList = await defaultEntryItemViewModel.InstallEntry(cancellationToken);
if (!removeFromList)
break;
DeviceProviderEntryViewModels.Remove(defaultEntryItemViewModel);
EssentialEntryViewModels.Remove(defaultEntryItemViewModel);
OtherEntryViewModels.Remove(defaultEntryItemViewModel);
CurrentEntries++;
}
Installed = true;
ContinueText = "Continue";
}
private void RemoveUnselectedEntries(ObservableCollection<DefaultEntryItemViewModel> entryViewModels)
{
List<DefaultEntryItemViewModel> toRemove = entryViewModels.Where(e => !e.ShouldInstall).ToList();
foreach (DefaultEntryItemViewModel defaultEntryItemViewModel in toRemove)
entryViewModels.Remove(defaultEntryItemViewModel);
}
private async Task GetDefaultEntries(CancellationToken cancellationToken)
{
if (!WorkshopReachable)
return false;
return;
FetchingDefaultEntries = true;
IOperationResult<IGetDefaultEntriesResult> result = await _client.GetDefaultEntries.ExecuteAsync(100, null, cancellationToken);
List<IEntrySummary> entries = result.Data?.EntriesV2?.Edges?.Select(e => e.Node).Cast<IEntrySummary>().ToList() ?? [];
@ -75,41 +116,25 @@ public partial class DefaultEntriesStepViewModel : WizardStepViewModel
entries.AddRange(result.Data.EntriesV2.Edges.Select(e => e.Node));
}
await Task.Delay(1000);
FetchingDefaultEntries = false;
TotalEntries = entries.Count;
if (entries.Count == 0)
return false;
DeviceProviderEntryViewModels.Clear();
EssentialEntryViewModels.Clear();
OtherEntryViewModels.Clear();
foreach (IEntrySummary entry in entries)
{
if (cancellationToken.IsCancellationRequested)
return false;
CurrentEntry = entry;
// Skip entries without a release and entries that are already installed
if (entry.LatestRelease == null || _workshopService.GetInstalledEntry(entry.Id) != null)
{
InstalledEntries++;
if (entry.DefaultEntryInfo == null)
continue;
DefaultEntryItemViewModel viewModel = _getDefaultEntryItemViewModel(entry);
viewModel.ShouldInstall = entry.DefaultEntryInfo.IsEssential;
if (entry.DefaultEntryInfo.IsDeviceProvider)
DeviceProviderEntryViewModels.Add(viewModel);
else if (entry.DefaultEntryInfo.IsEssential)
EssentialEntryViewModels.Add(viewModel);
else
OtherEntryViewModels.Add(viewModel);
}
EntryInstallResult installResult = await _workshopService.InstallEntry(entry, entry.LatestRelease, _currentEntryProgress, cancellationToken);
// Unlikely as default entries do nothing fancy
if (!installResult.IsSuccess)
{
await _windowService.CreateContentDialog().WithTitle("Failed to install entry")
.WithContent($"Failed to install entry '{entry.Name}' ({entry.Id}): {installResult.Message}")
.WithCloseButtonText("Skip and continue")
.ShowAsync();
}
InstalledEntries++;
}
return true;
FetchingDefaultEntries = false;
}
}

View File

@ -0,0 +1,72 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:steps="clr-namespace:Artemis.UI.Screens.StartupWizard.Steps"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.StartupWizard.Steps.DefaultEntryItemView"
x:DataType="steps:DefaultEntryItemViewModel">
<UserControl.Resources>
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
<converters:DateTimeConverter x:Key="DateTimeConverter" />
</UserControl.Resources>
<Border Classes="card" Padding="15" Margin="0 5">
<Grid ColumnDefinitions="Auto,*,Auto,Auto" RowDefinitions="*, Auto">
<!-- Icon -->
<Border Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
CornerRadius="6"
VerticalAlignment="Center"
Margin="0 0 10 0"
Width="80"
Height="80"
ClipToBounds="True">
<Image Stretch="UniformToFill" il:ImageLoader.Source="{CompiledBinding Entry.Id, Converter={StaticResource EntryIconUriConverter}, Mode=OneWay}" />
</Border>
<!-- Body -->
<Grid Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto">
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Classes="h5" Margin="0 0 0 5" TextTrimming="CharacterEllipsis" Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
</StackPanel>
<TextBlock Grid.Row="1"
Classes="subtitle"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
Text="{CompiledBinding Entry.Summary, FallbackValue=Summary}" />
<ItemsControl Grid.Row="2" ItemsSource="{CompiledBinding Entry.Categories}" Margin="0 8 0 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Margin="0 0 3 0" />
<TextBlock Text="{CompiledBinding Name}" TextTrimming="CharacterEllipsis" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
<!-- Management -->
<Border Grid.Column="3" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0" IsVisible="{CompiledBinding !IsInstalled}">
<StackPanel VerticalAlignment="Center">
<CheckBox MinHeight="26" Margin="0 4 0 0" IsChecked="{CompiledBinding ShouldInstall}">Install</CheckBox>
</StackPanel>
</Border>
<Border Grid.Column="3" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0" IsVisible="{CompiledBinding IsInstalled}">
<StackPanel VerticalAlignment="Center">
<TextBlock>Already installed</TextBlock>
</StackPanel>
</Border>
</Grid>
</Border>
</UserControl>

View File

@ -0,0 +1,11 @@
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class DefaultEntryItemView : ReactiveUserControl<StartupWizard.Steps.DefaultEntryItemViewModel>
{
public DefaultEntryItemView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,86 @@
using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.DryIoc.Factories;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Utilities;
using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
using Artemis.WebClient.Workshop.Models;
using Artemis.WebClient.Workshop.Services;
using PropertyChanged.SourceGenerator;
using ReactiveUI;
using StrawberryShake;
namespace Artemis.UI.Screens.StartupWizard.Steps;
public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
{
private readonly IWorkshopService _workshopService;
private readonly IWindowService _windowService;
private readonly IPluginManagementService _pluginManagementService;
private readonly ISettingsVmFactory _settingsVmFactory;
private readonly Progress<StreamProgress> _progress = new();
[Notify] private bool _isInstalled;
[Notify] private bool _shouldInstall;
public DefaultEntryItemViewModel(IEntrySummary entry, IWorkshopService workshopService, IWindowService windowService, IPluginManagementService pluginManagementService, ISettingsVmFactory settingsVmFactory)
{
_workshopService = workshopService;
_windowService = windowService;
_pluginManagementService = pluginManagementService;
_settingsVmFactory = settingsVmFactory;
Entry = entry;
this.WhenActivated((CompositeDisposable _) => { IsInstalled = workshopService.GetInstalledEntry(entry.Id) != null; });
}
public IEntrySummary Entry { get; }
public async Task<bool> InstallEntry(CancellationToken cancellationToken)
{
if (IsInstalled || !ShouldInstall || Entry.LatestRelease == null)
return true;
// Most entries install so quick it looks broken without a small delay
Task minimumDelay = Task.Delay(100, cancellationToken);
EntryInstallResult result = await _workshopService.InstallEntry(Entry, Entry.LatestRelease, _progress, cancellationToken);
await minimumDelay;
if (!result.IsSuccess)
{
await _windowService.CreateContentDialog().WithTitle("Failed to install entry")
.WithContent($"Failed to install entry '{Entry.Name}' ({Entry.Id}): {result.Message}")
.WithCloseButtonText("Skip and continue")
.ShowAsync();
}
// If the entry is a plugin, enable the plugin
else if (result.Entry?.EntryType == EntryType.Plugin)
{
await EnablePlugin(result.Entry);
}
return result.IsSuccess;
}
private async Task EnablePlugin(InstalledEntry entry)
{
if (!entry.TryGetMetadata("PluginId", out Guid pluginId))
throw new InvalidOperationException("Plugin entry does not contain a PluginId metadata value.");
Plugin? plugin = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginId);
if (plugin == null)
throw new InvalidOperationException($"Plugin with id '{pluginId}' does not exist.");
// There's quite a bit of UI involved in enabling a plugin, borrowing the PluginSettingsViewModel for this
PluginViewModel pluginViewModel = _settingsVmFactory.PluginViewModel(plugin, ReactiveCommand.Create(() => { }));
await pluginViewModel.UpdateEnabled(true);
}
}

View File

@ -64,7 +64,11 @@
<Label Target="Summary" Margin="0 5 0 0">Summary</Label>
<TextBox Name="Summary" Text="{CompiledBinding Summary}"></TextBox>
<CheckBox IsVisible="{CompiledBinding IsAdministrator}" IsChecked="{CompiledBinding IsDefault}">Download by default (admin only)</CheckBox>
<StackPanel IsVisible="{CompiledBinding IsAdministrator}" Orientation="Horizontal">
<CheckBox IsChecked="{CompiledBinding IsDefault}">Download by default (admin only)</CheckBox>
<CheckBox IsEnabled="{CompiledBinding IsDefault}" IsChecked="{CompiledBinding IsEssential}">Essential</CheckBox>
<CheckBox IsEnabled="{CompiledBinding IsDefault}" IsChecked="{CompiledBinding IsDeviceProvider}">Device provider</CheckBox>
</StackPanel>
</StackPanel>
</Grid>
@ -94,7 +98,7 @@
<tagsInput:TagsInput Tags="{CompiledBinding Tags}" />
</StackPanel>
<controls:SplitMarkdownEditor Grid.Row="1" Title="Description" Markdown="{CompiledBinding Description}"/>
<controls:SplitMarkdownEditor Grid.Row="1" Title="Description" Markdown="{CompiledBinding Description}" />
<TextBlock Grid.Row="2"
Foreground="{DynamicResource SystemFillColorCriticalBrush}"

View File

@ -36,6 +36,8 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
[Notify] private string _summary = string.Empty;
[Notify] private string _description = string.Empty;
[Notify] private bool _isDefault;
[Notify] private bool _isEssential;
[Notify] private bool _isDeviceProvider;
[Notify] private Bitmap? _iconBitmap;
[Notify(Setter.Private)] private bool _iconChanged;

View File

@ -105,7 +105,9 @@ public partial class SubmissionDetailsViewModel : RoutableScreen
specificationsViewModel.Name = Entry.Name;
specificationsViewModel.Summary = Entry.Summary;
specificationsViewModel.Description = Entry.Description;
specificationsViewModel.IsDefault = Entry.IsDefault;
specificationsViewModel.IsDefault = Entry.DefaultEntryInfo != null;
specificationsViewModel.IsEssential = Entry.DefaultEntryInfo?.IsEssential ?? false;
specificationsViewModel.IsDeviceProvider = Entry.DefaultEntryInfo?.IsDeviceProvider ?? false;
specificationsViewModel.PreselectedCategories = Entry.Categories.Select(c => c.Id).ToList();
specificationsViewModel.Tags.Clear();
@ -170,14 +172,29 @@ public partial class SubmissionDetailsViewModel : RoutableScreen
HasChanges = EntrySpecificationsViewModel.Name != Entry.Name ||
EntrySpecificationsViewModel.Description != Entry.Description ||
EntrySpecificationsViewModel.Summary != Entry.Summary ||
EntrySpecificationsViewModel.IsDefault != Entry.IsDefault ||
EntrySpecificationsViewModel.IconChanged ||
HasAdminChanges() ||
!tags.SequenceEqual(Entry.Tags.Select(t => t.Name).OrderBy(t => t)) ||
!categories.SequenceEqual(Entry.Categories.Select(c => c.Id).OrderBy(c => c)) ||
Images.Any(i => i.HasChanges) ||
_removedImages.Any();
}
private bool HasAdminChanges()
{
if (EntrySpecificationsViewModel == null || Entry == null)
return false;
bool isDefault = Entry.DefaultEntryInfo != null;
bool isEssential = Entry.DefaultEntryInfo?.IsEssential ?? false;
bool isDeviceProvider = Entry.DefaultEntryInfo?.IsDeviceProvider ?? false;
return EntrySpecificationsViewModel.IsDefault != isDefault ||
(EntrySpecificationsViewModel.IsDefault && (
EntrySpecificationsViewModel.IsEssential != isEssential ||
EntrySpecificationsViewModel.IsDeviceProvider != isDeviceProvider));
}
private async Task ExecuteDiscardChanges()
{
await ApplyDetailsFromEntry(CancellationToken.None);
@ -194,9 +211,15 @@ public partial class SubmissionDetailsViewModel : RoutableScreen
Name = EntrySpecificationsViewModel.Name,
Summary = EntrySpecificationsViewModel.Summary,
Description = EntrySpecificationsViewModel.Description,
IsDefault = EntrySpecificationsViewModel.IsDefault,
Categories = EntrySpecificationsViewModel.SelectedCategories,
Tags = EntrySpecificationsViewModel.Tags
Tags = EntrySpecificationsViewModel.Tags,
DefaultEntryInfo = EntrySpecificationsViewModel.IsDefault
? new DefaultEntryInfoInput
{
IsEssential = EntrySpecificationsViewModel.IsEssential,
IsDeviceProvider = EntrySpecificationsViewModel.IsDeviceProvider
}
: null
};
IOperationResult<IUpdateEntryResult> result = await _client.UpdateEntry.ExecuteAsync(input, cancellationToken);

View File

@ -33,6 +33,8 @@ public class SubmissionWizardState : IDisposable
public string Summary { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public bool IsDefault { get; set; }
public bool IsEssential { get; set; }
public bool IsDeviceProvider { get; set; }
public List<long> Categories { get; set; } = new();
public List<string> Tags { get; set; } = new();

View File

@ -66,6 +66,8 @@ public partial class SpecificationsStepViewModel : SubmissionViewModel
viewModel.Summary = State.Summary;
viewModel.Description = State.Description;
viewModel.IsDefault = State.IsDefault;
viewModel.IsEssential = State.IsEssential;
viewModel.IsDeviceProvider = State.IsDeviceProvider;
// Tags
viewModel.Tags.Clear();
@ -95,6 +97,8 @@ public partial class SpecificationsStepViewModel : SubmissionViewModel
State.Summary = EntrySpecificationsViewModel.Summary;
State.Description = EntrySpecificationsViewModel.Description;
State.IsDefault = EntrySpecificationsViewModel.IsDefault;
State.IsEssential = EntrySpecificationsViewModel.IsEssential;
State.IsDeviceProvider = EntrySpecificationsViewModel.IsDeviceProvider;
// Categories and tasks
State.Categories = EntrySpecificationsViewModel.Categories.Where(c => c.IsSelected).Select(c => c.Id).ToList();

View File

@ -105,7 +105,14 @@ public partial class UploadStepViewModel : SubmissionViewModel
Summary = State.Summary,
Description = State.Description,
Categories = State.Categories,
Tags = State.Tags
Tags = State.Tags,
DefaultEntryInfo = State.IsDefault
? new DefaultEntryInfoInput
{
IsEssential = State.IsEssential,
IsDeviceProvider = State.IsDeviceProvider
}
: null
}, cancellationToken);
result.EnsureNoErrors();

View File

@ -26,7 +26,9 @@ fragment submittedEntry on Entry {
entryType
downloads
createdAt
isDefault
defaultEntryInfo {
...defaultEntryInfo
}
latestRelease {
...release
}
@ -48,6 +50,9 @@ fragment entrySummary on Entry {
categories {
...category
}
defaultEntryInfo {
...defaultEntryInfo
}
}
fragment entryDetails on Entry {
@ -96,3 +101,8 @@ fragment pluginInfo on PluginInfo {
supportsLinux
supportsOSX
}
fragment defaultEntryInfo on DefaultEntryInfo {
isEssential
isDeviceProvider
}

View File

@ -20,8 +20,12 @@ query GetPopularEntries {
}
}
query GetDefaultEntries($first: Int $after: String) {
entriesV2(where: {isDefault: {eq: true}} first: $first after: $after) {
query GetDefaultEntries($first: Int, $after: String) {
entriesV2(
where: { defaultEntryInfo: { entryId: { gt: 0 } } }
first: $first
after: $after
) {
totalCount
pageInfo {
hasNextPage

View File

@ -2,10 +2,10 @@ namespace Artemis.WebClient.Workshop;
public static class WorkshopConstants
{
// public const string AUTHORITY_URL = "https://localhost:5001";
// public const string WORKSHOP_URL = "https://localhost:7281";
public const string AUTHORITY_URL = "https://identity.artemis-rgb.com";
public const string WORKSHOP_URL = "https://workshop.artemis-rgb.com";
public const string AUTHORITY_URL = "https://localhost:5001";
public const string WORKSHOP_URL = "https://localhost:7281";
// public const string AUTHORITY_URL = "https://identity.artemis-rgb.com";
// public const string WORKSHOP_URL = "https://workshop.artemis-rgb.com";
public const string IDENTITY_CLIENT_NAME = "IdentityApiClient";
public const string WORKSHOP_CLIENT_NAME = "WorkshopApiClient";
}

View File

@ -2,7 +2,7 @@ schema: schema.graphql
extensions:
endpoints:
Default GraphQL Endpoint:
url: https://workshop.artemis-rgb.com/graphql
url: https://localhost:7281/graphql/
headers:
user-agent: JS GraphQL
introspect: true

View File

@ -6,9 +6,9 @@ schema {
}
type Category {
icon: String!
id: Long!
name: String!
icon: String!
}
"Information about the offset pagination."
@ -19,25 +19,31 @@ type CollectionSegmentInfo {
hasPreviousPage: Boolean!
}
type DefaultEntryInfo {
entryId: Long!
isEssential: Boolean!
isDeviceProvider: Boolean!
}
"A segment of a collection."
type EntriesCollectionSegment {
"A flattened list of the items."
items: [Entry!]
"Information to aid in pagination."
pageInfo: CollectionSegmentInfo!
totalCount: Int!
"A flattened list of the items."
items: [Entry!]
totalCount: Int! @cost(weight: "10")
}
"A connection to a list of items."
type EntriesV2Connection {
"Information to aid in pagination."
pageInfo: PageInfo!
"A list of edges."
edges: [EntriesV2Edge!]
"A flattened list of the nodes."
nodes: [Entry!]
"Information to aid in pagination."
pageInfo: PageInfo!
"Identifies the total count of items in the connection."
totalCount: Int!
totalCount: Int! @cost(weight: "10")
}
"An edge in a connection."
@ -49,142 +55,128 @@ type EntriesV2Edge {
}
type Entry {
author: String!
authorId: UUID!
categories: [Category!]!
id: Long!
entryType: EntryType!
createdAt: DateTime!
dependantReleases: [Release!]!
authorId: UUID!
author: String!
isOfficial: Boolean!
name: String!
summary: String!
description: String!
downloads: Long!
entryType: EntryType!
icon: Image
iconId: UUID
id: Long!
images: [Image!]!
isDefault: Boolean!
isOfficial: Boolean!
latestRelease: Release
icon: Image
latestReleaseId: Long
layoutInfo: [LayoutInfo!]!
name: String!
latestRelease: Release
pluginInfo: PluginInfo
popularityScore: Float!
releases: [Release!]!
summary: String!
layoutInfo: [LayoutInfo!]!
defaultEntryInfo: DefaultEntryInfo
categories: [Category!]!
tags: [Tag!]!
images: [Image!]!
releases: [Release!]!
dependantReleases: [Release!]!
}
type Image {
id: UUID!
name: String!
description: String
width: Int!
height: Int!
size: Long!
mimeType: String!
entry: Entry
entryId: Long
height: Int!
id: UUID!
mimeType: String!
name: String!
size: Long!
width: Int!
}
type LayoutInfo {
id: Long!
deviceProvider: UUID!
deviceType: RGBDeviceType!
entry: Entry!
entryId: Long!
id: Long!
logicalLayout: String
vendor: String!
model: String!
physicalLayout: KeyboardLayoutType
vendor: String!
logicalLayout: String
entryId: Long!
entry: Entry!
}
type Mutation {
addEntry(input: CreateEntryInput!): Entry
addLayoutInfo(input: CreateLayoutInfoInput!): LayoutInfo
removeEntry(id: Long!): Entry
removeLayoutInfo(id: Long!): LayoutInfo!
removeRelease(id: Long!): Release!
setLayoutInfo(input: SetLayoutInfoInput!): [LayoutInfo!]!
updateEntry(input: UpdateEntryInput!): Entry
updateEntryImage(input: UpdateEntryImageInput!): Image
updateRelease(input: UpdateReleaseInput!): Release
addEntry(input: CreateEntryInput!): Entry @authorize @cost(weight: "10")
updateEntry(input: UpdateEntryInput!): Entry @authorize @cost(weight: "10")
removeEntry(id: Long!): Entry @authorize @cost(weight: "10")
updateEntryImage(input: UpdateEntryImageInput!): Image @authorize @cost(weight: "10")
setLayoutInfo(input: SetLayoutInfoInput!): [LayoutInfo!]! @authorize @cost(weight: "10")
addLayoutInfo(input: CreateLayoutInfoInput!): LayoutInfo @authorize @cost(weight: "10")
removeLayoutInfo(id: Long!): LayoutInfo! @authorize @cost(weight: "10")
updateRelease(input: UpdateReleaseInput!): Release @authorize @cost(weight: "10")
removeRelease(id: Long!): Release! @authorize @cost(weight: "10")
}
"Information about pagination in a connection."
type PageInfo {
"When paginating forwards, the cursor to continue."
endCursor: String
"Indicates whether more edges exist following the set defined by the clients arguments."
hasNextPage: Boolean!
"Indicates whether more edges exist prior the set defined by the clients arguments."
hasPreviousPage: Boolean!
"When paginating backwards, the cursor to continue."
startCursor: String
"When paginating forwards, the cursor to continue."
endCursor: String
}
type PluginInfo {
api: Int
entry: Entry!
entryId: Long!
helpPage: String
minmumVersion: String
entry: Entry!
pluginGuid: UUID!
api: Int
minmumVersion: String
website: String
helpPage: String
repository: String
requiresAdmin: Boolean!
supportsWindows: Boolean!
supportsLinux: Boolean!
supportsOSX: Boolean!
supportsWindows: Boolean!
website: String
}
"A segment of a collection."
type PluginInfosCollectionSegment {
"A flattened list of the items."
items: [PluginInfo!]
"Information to aid in pagination."
pageInfo: CollectionSegmentInfo!
totalCount: Int!
"A flattened list of the items."
items: [PluginInfo!]
totalCount: Int! @cost(weight: "10")
}
type Query {
categories(order: [CategorySortInput!], where: CategoryFilterInput): [Category!]!
entries(order: [EntrySortInput!], popular: Boolean, search: String, skip: Int, take: Int, where: EntryFilterInput): EntriesCollectionSegment
entriesV2(
"Returns the elements in the list that come after the specified cursor."
after: String,
"Returns the elements in the list that come before the specified cursor."
before: String,
"Returns the first _n_ elements from the list."
first: Int,
"Returns the last _n_ elements from the list."
last: Int,
order: [EntrySortInput!],
popular: Boolean,
search: String,
where: EntryFilterInput
): EntriesV2Connection
entry(id: Long!): Entry
pluginInfo(pluginGuid: UUID!): PluginInfo
pluginInfos(order: [PluginInfoSortInput!], skip: Int, take: Int, where: PluginInfoFilterInput): PluginInfosCollectionSegment
popularEntries(where: EntryFilterInput): [Entry!]!
release(id: Long!): Release
searchEntries(input: String!, order: [EntrySortInput!], type: EntryType, where: EntryFilterInput): [Entry!]!
searchKeyboardLayout(deviceProvider: UUID!, logicalLayout: String, model: String!, physicalLayout: KeyboardLayoutType!, vendor: String!): LayoutInfo
searchLayout(deviceProvider: UUID!, deviceType: RGBDeviceType!, model: String!, vendor: String!): LayoutInfo
submittedEntries(order: [EntrySortInput!], where: EntryFilterInput): [Entry!]!
categories(order: [CategorySortInput!] @cost(weight: "10") where: CategoryFilterInput @cost(weight: "10")): [Category!]! @cost(weight: "10")
entries(skip: Int take: Int search: String popular: Boolean order: [EntrySortInput!] @cost(weight: "10") where: EntryFilterInput @cost(weight: "10")): EntriesCollectionSegment @listSize(assumedSize: 100, slicingArguments: [ "take" ], slicingArgumentDefaultValue: 10, sizedFields: [ "items" ], requireOneSlicingArgument: false) @cost(weight: "10")
entriesV2(search: String popular: Boolean "Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String order: [EntrySortInput!] @cost(weight: "10") where: EntryFilterInput @cost(weight: "10")): EntriesV2Connection @listSize(assumedSize: 100, slicingArguments: [ "first", "last" ], slicingArgumentDefaultValue: 10, sizedFields: [ "edges", "nodes" ], requireOneSlicingArgument: false) @cost(weight: "10")
entry(id: Long!): Entry @cost(weight: "10")
submittedEntries(order: [EntrySortInput!] @cost(weight: "10") where: EntryFilterInput @cost(weight: "10")): [Entry!]! @authorize @cost(weight: "10")
popularEntries(where: EntryFilterInput @cost(weight: "10")): [Entry!]! @cost(weight: "10")
searchEntries(input: String! type: EntryType order: [EntrySortInput!] @cost(weight: "10") where: EntryFilterInput @cost(weight: "10")): [Entry!]! @cost(weight: "10")
searchLayout(deviceProvider: UUID! deviceType: RGBDeviceType! vendor: String! model: String!): LayoutInfo @cost(weight: "10")
searchKeyboardLayout(deviceProvider: UUID! vendor: String! model: String! physicalLayout: KeyboardLayoutType! logicalLayout: String): LayoutInfo @cost(weight: "10")
pluginInfos(skip: Int take: Int order: [PluginInfoSortInput!] @cost(weight: "10") where: PluginInfoFilterInput @cost(weight: "10")): PluginInfosCollectionSegment @listSize(assumedSize: 100, slicingArguments: [ "take" ], slicingArgumentDefaultValue: 10, sizedFields: [ "items" ], requireOneSlicingArgument: false) @cost(weight: "10")
pluginInfo(pluginGuid: UUID!): PluginInfo @cost(weight: "10")
release(id: Long!): Release @cost(weight: "10")
}
type Release {
id: Long!
version: String!
changelog: String
createdAt: DateTime!
dependencies: [Entry!]!
downloadSize: Long!
downloads: Long!
downloadSize: Long!
md5Hash: String
entry: Entry!
entryId: Long!
id: Long!
md5Hash: String
version: String!
dependencies: [Entry!]!
}
type Tag {
@ -192,379 +184,324 @@ type Tag {
name: String!
}
enum ApplyPolicy {
AFTER_RESOLVER
BEFORE_RESOLVER
VALIDATION
}
enum EntryType {
LAYOUT
PLUGIN
PROFILE
}
enum KeyboardLayoutType {
ABNT
ANSI
ISO
JIS
KS
UNKNOWN
}
enum RGBDeviceType {
ALL
COOLER
DRAM
FAN
GAME_CONTROLLER
GRAPHICS_CARD
HEADSET
HEADSET_STAND
KEYBOARD
KEYPAD
LED_CONTROLLER
LED_MATRIX
LED_STRIPE
MAINBOARD
MONITOR
MOUSE
MOUSEPAD
NONE
SPEAKER
UNKNOWN
}
enum SortEnumType {
ASC
DESC
}
"The `DateTime` scalar represents an ISO-8601 compliant date time type."
scalar DateTime
"The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1."
scalar Long
scalar UUID
input BooleanOperationFilterInput {
eq: Boolean
neq: Boolean
eq: Boolean @cost(weight: "10")
neq: Boolean @cost(weight: "10")
}
input CategoryFilterInput {
and: [CategoryFilterInput!]
icon: StringOperationFilterInput
or: [CategoryFilterInput!]
id: LongOperationFilterInput
name: StringOperationFilterInput
or: [CategoryFilterInput!]
icon: StringOperationFilterInput
}
input CategorySortInput {
icon: SortEnumType
id: SortEnumType
name: SortEnumType
id: SortEnumType @cost(weight: "10")
name: SortEnumType @cost(weight: "10")
icon: SortEnumType @cost(weight: "10")
}
input CreateEntryInput {
categories: [Long!]!
description: String!
entryType: EntryType!
isDefault: Boolean!
name: String!
summary: String!
description: String!
categories: [Long!]!
tags: [String!]!
defaultEntryInfo: DefaultEntryInfoInput
}
input CreateLayoutInfoInput {
entryId: Long!
deviceProvider: UUID!
deviceType: RGBDeviceType!
entryId: Long!
logicalLayout: String
vendor: String!
model: String!
physicalLayout: KeyboardLayoutType
vendor: String!
logicalLayout: String
}
input DateTimeOperationFilterInput {
eq: DateTime
gt: DateTime
gte: DateTime
in: [DateTime]
lt: DateTime
lte: DateTime
neq: DateTime
ngt: DateTime
ngte: DateTime
nin: [DateTime]
nlt: DateTime
nlte: DateTime
eq: DateTime @cost(weight: "10")
neq: DateTime @cost(weight: "10")
in: [DateTime] @cost(weight: "10")
nin: [DateTime] @cost(weight: "10")
gt: DateTime @cost(weight: "10")
ngt: DateTime @cost(weight: "10")
gte: DateTime @cost(weight: "10")
ngte: DateTime @cost(weight: "10")
lt: DateTime @cost(weight: "10")
nlt: DateTime @cost(weight: "10")
lte: DateTime @cost(weight: "10")
nlte: DateTime @cost(weight: "10")
}
input DefaultEntryInfoFilterInput {
and: [DefaultEntryInfoFilterInput!]
or: [DefaultEntryInfoFilterInput!]
entryId: LongOperationFilterInput
isEssential: BooleanOperationFilterInput
isDeviceProvider: BooleanOperationFilterInput
}
input DefaultEntryInfoInput {
isEssential: Boolean!
isDeviceProvider: Boolean!
}
input DefaultEntryInfoSortInput {
entryId: SortEnumType @cost(weight: "10")
isEssential: SortEnumType @cost(weight: "10")
isDeviceProvider: SortEnumType @cost(weight: "10")
}
input EntryFilterInput {
and: [EntryFilterInput!]
author: StringOperationFilterInput
authorId: UuidOperationFilterInput
categories: ListFilterInputTypeOfCategoryFilterInput
or: [EntryFilterInput!]
id: LongOperationFilterInput
entryType: EntryTypeOperationFilterInput
createdAt: DateTimeOperationFilterInput
dependantReleases: ListFilterInputTypeOfReleaseFilterInput
authorId: UuidOperationFilterInput
author: StringOperationFilterInput
isOfficial: BooleanOperationFilterInput
name: StringOperationFilterInput
summary: StringOperationFilterInput
description: StringOperationFilterInput
downloads: LongOperationFilterInput
entryType: EntryTypeOperationFilterInput
icon: ImageFilterInput
iconId: UuidOperationFilterInput
id: LongOperationFilterInput
images: ListFilterInputTypeOfImageFilterInput
isDefault: BooleanOperationFilterInput
isOfficial: BooleanOperationFilterInput
latestRelease: ReleaseFilterInput
icon: ImageFilterInput
latestReleaseId: LongOperationFilterInput
layoutInfo: ListFilterInputTypeOfLayoutInfoFilterInput
name: StringOperationFilterInput
or: [EntryFilterInput!]
latestRelease: ReleaseFilterInput
pluginInfo: PluginInfoFilterInput
popularityScore: FloatOperationFilterInput
releases: ListFilterInputTypeOfReleaseFilterInput
summary: StringOperationFilterInput
layoutInfo: ListFilterInputTypeOfLayoutInfoFilterInput
defaultEntryInfo: DefaultEntryInfoFilterInput
categories: ListFilterInputTypeOfCategoryFilterInput
tags: ListFilterInputTypeOfTagFilterInput
images: ListFilterInputTypeOfImageFilterInput
releases: ListFilterInputTypeOfReleaseFilterInput
dependantReleases: ListFilterInputTypeOfReleaseFilterInput
}
input EntrySortInput {
author: SortEnumType
authorId: SortEnumType
createdAt: SortEnumType
description: SortEnumType
downloads: SortEnumType
entryType: SortEnumType
icon: ImageSortInput
iconId: SortEnumType
id: SortEnumType
isDefault: SortEnumType
isOfficial: SortEnumType
latestRelease: ReleaseSortInput
latestReleaseId: SortEnumType
name: SortEnumType
pluginInfo: PluginInfoSortInput
popularityScore: SortEnumType
summary: SortEnumType
id: SortEnumType @cost(weight: "10")
entryType: SortEnumType @cost(weight: "10")
createdAt: SortEnumType @cost(weight: "10")
authorId: SortEnumType @cost(weight: "10")
author: SortEnumType @cost(weight: "10")
isOfficial: SortEnumType @cost(weight: "10")
name: SortEnumType @cost(weight: "10")
summary: SortEnumType @cost(weight: "10")
description: SortEnumType @cost(weight: "10")
downloads: SortEnumType @cost(weight: "10")
iconId: SortEnumType @cost(weight: "10")
icon: ImageSortInput @cost(weight: "10")
latestReleaseId: SortEnumType @cost(weight: "10")
latestRelease: ReleaseSortInput @cost(weight: "10")
pluginInfo: PluginInfoSortInput @cost(weight: "10")
defaultEntryInfo: DefaultEntryInfoSortInput @cost(weight: "10")
}
input EntryTypeOperationFilterInput {
eq: EntryType
in: [EntryType!]
neq: EntryType
nin: [EntryType!]
}
input FloatOperationFilterInput {
eq: Float
gt: Float
gte: Float
in: [Float]
lt: Float
lte: Float
neq: Float
ngt: Float
ngte: Float
nin: [Float]
nlt: Float
nlte: Float
eq: EntryType @cost(weight: "10")
neq: EntryType @cost(weight: "10")
in: [EntryType!] @cost(weight: "10")
nin: [EntryType!] @cost(weight: "10")
}
input ImageFilterInput {
and: [ImageFilterInput!]
or: [ImageFilterInput!]
id: UuidOperationFilterInput
name: StringOperationFilterInput
description: StringOperationFilterInput
width: IntOperationFilterInput
height: IntOperationFilterInput
size: LongOperationFilterInput
mimeType: StringOperationFilterInput
entry: EntryFilterInput
entryId: LongOperationFilterInput
height: IntOperationFilterInput
id: UuidOperationFilterInput
mimeType: StringOperationFilterInput
name: StringOperationFilterInput
or: [ImageFilterInput!]
size: LongOperationFilterInput
width: IntOperationFilterInput
}
input ImageSortInput {
description: SortEnumType
entry: EntrySortInput
entryId: SortEnumType
height: SortEnumType
id: SortEnumType
mimeType: SortEnumType
name: SortEnumType
size: SortEnumType
width: SortEnumType
id: SortEnumType @cost(weight: "10")
name: SortEnumType @cost(weight: "10")
description: SortEnumType @cost(weight: "10")
width: SortEnumType @cost(weight: "10")
height: SortEnumType @cost(weight: "10")
size: SortEnumType @cost(weight: "10")
mimeType: SortEnumType @cost(weight: "10")
entry: EntrySortInput @cost(weight: "10")
entryId: SortEnumType @cost(weight: "10")
}
input IntOperationFilterInput {
eq: Int
gt: Int
gte: Int
in: [Int]
lt: Int
lte: Int
neq: Int
ngt: Int
ngte: Int
nin: [Int]
nlt: Int
nlte: Int
eq: Int @cost(weight: "10")
neq: Int @cost(weight: "10")
in: [Int] @cost(weight: "10")
nin: [Int] @cost(weight: "10")
gt: Int @cost(weight: "10")
ngt: Int @cost(weight: "10")
gte: Int @cost(weight: "10")
ngte: Int @cost(weight: "10")
lt: Int @cost(weight: "10")
nlt: Int @cost(weight: "10")
lte: Int @cost(weight: "10")
nlte: Int @cost(weight: "10")
}
input LayoutInfoFilterInput {
and: [LayoutInfoFilterInput!]
or: [LayoutInfoFilterInput!]
id: LongOperationFilterInput
deviceProvider: UuidOperationFilterInput
deviceType: RGBDeviceTypeOperationFilterInput
entry: EntryFilterInput
entryId: LongOperationFilterInput
id: LongOperationFilterInput
logicalLayout: StringOperationFilterInput
model: StringOperationFilterInput
or: [LayoutInfoFilterInput!]
physicalLayout: NullableOfKeyboardLayoutTypeOperationFilterInput
vendor: StringOperationFilterInput
model: StringOperationFilterInput
physicalLayout: NullableOfKeyboardLayoutTypeOperationFilterInput
logicalLayout: StringOperationFilterInput
entryId: LongOperationFilterInput
entry: EntryFilterInput
}
input LayoutInfoInput {
deviceProvider: UUID!
deviceType: RGBDeviceType!
logicalLayout: String
vendor: String!
model: String!
physicalLayout: KeyboardLayoutType
vendor: String!
logicalLayout: String
}
input ListFilterInputTypeOfCategoryFilterInput {
all: CategoryFilterInput
any: Boolean
none: CategoryFilterInput
some: CategoryFilterInput
all: CategoryFilterInput @cost(weight: "10")
none: CategoryFilterInput @cost(weight: "10")
some: CategoryFilterInput @cost(weight: "10")
any: Boolean @cost(weight: "10")
}
input ListFilterInputTypeOfEntryFilterInput {
all: EntryFilterInput
any: Boolean
none: EntryFilterInput
some: EntryFilterInput
all: EntryFilterInput @cost(weight: "10")
none: EntryFilterInput @cost(weight: "10")
some: EntryFilterInput @cost(weight: "10")
any: Boolean @cost(weight: "10")
}
input ListFilterInputTypeOfImageFilterInput {
all: ImageFilterInput
any: Boolean
none: ImageFilterInput
some: ImageFilterInput
all: ImageFilterInput @cost(weight: "10")
none: ImageFilterInput @cost(weight: "10")
some: ImageFilterInput @cost(weight: "10")
any: Boolean @cost(weight: "10")
}
input ListFilterInputTypeOfLayoutInfoFilterInput {
all: LayoutInfoFilterInput
any: Boolean
none: LayoutInfoFilterInput
some: LayoutInfoFilterInput
all: LayoutInfoFilterInput @cost(weight: "10")
none: LayoutInfoFilterInput @cost(weight: "10")
some: LayoutInfoFilterInput @cost(weight: "10")
any: Boolean @cost(weight: "10")
}
input ListFilterInputTypeOfReleaseFilterInput {
all: ReleaseFilterInput
any: Boolean
none: ReleaseFilterInput
some: ReleaseFilterInput
all: ReleaseFilterInput @cost(weight: "10")
none: ReleaseFilterInput @cost(weight: "10")
some: ReleaseFilterInput @cost(weight: "10")
any: Boolean @cost(weight: "10")
}
input ListFilterInputTypeOfTagFilterInput {
all: TagFilterInput
any: Boolean
none: TagFilterInput
some: TagFilterInput
all: TagFilterInput @cost(weight: "10")
none: TagFilterInput @cost(weight: "10")
some: TagFilterInput @cost(weight: "10")
any: Boolean @cost(weight: "10")
}
input LongOperationFilterInput {
eq: Long
gt: Long
gte: Long
in: [Long]
lt: Long
lte: Long
neq: Long
ngt: Long
ngte: Long
nin: [Long]
nlt: Long
nlte: Long
eq: Long @cost(weight: "10")
neq: Long @cost(weight: "10")
in: [Long] @cost(weight: "10")
nin: [Long] @cost(weight: "10")
gt: Long @cost(weight: "10")
ngt: Long @cost(weight: "10")
gte: Long @cost(weight: "10")
ngte: Long @cost(weight: "10")
lt: Long @cost(weight: "10")
nlt: Long @cost(weight: "10")
lte: Long @cost(weight: "10")
nlte: Long @cost(weight: "10")
}
input NullableOfKeyboardLayoutTypeOperationFilterInput {
eq: KeyboardLayoutType
in: [KeyboardLayoutType]
neq: KeyboardLayoutType
nin: [KeyboardLayoutType]
eq: KeyboardLayoutType @cost(weight: "10")
neq: KeyboardLayoutType @cost(weight: "10")
in: [KeyboardLayoutType] @cost(weight: "10")
nin: [KeyboardLayoutType] @cost(weight: "10")
}
input PluginInfoFilterInput {
and: [PluginInfoFilterInput!]
api: IntOperationFilterInput
entry: EntryFilterInput
entryId: LongOperationFilterInput
helpPage: StringOperationFilterInput
minmumVersion: StringOperationFilterInput
or: [PluginInfoFilterInput!]
entryId: LongOperationFilterInput
entry: EntryFilterInput
pluginGuid: UuidOperationFilterInput
api: IntOperationFilterInput
minmumVersion: StringOperationFilterInput
website: StringOperationFilterInput
helpPage: StringOperationFilterInput
repository: StringOperationFilterInput
requiresAdmin: BooleanOperationFilterInput
supportsWindows: BooleanOperationFilterInput
supportsLinux: BooleanOperationFilterInput
supportsOSX: BooleanOperationFilterInput
supportsWindows: BooleanOperationFilterInput
website: StringOperationFilterInput
}
input PluginInfoSortInput {
api: SortEnumType
entry: EntrySortInput
entryId: SortEnumType
helpPage: SortEnumType
minmumVersion: SortEnumType
pluginGuid: SortEnumType
repository: SortEnumType
requiresAdmin: SortEnumType
supportsLinux: SortEnumType
supportsOSX: SortEnumType
supportsWindows: SortEnumType
website: SortEnumType
entryId: SortEnumType @cost(weight: "10")
entry: EntrySortInput @cost(weight: "10")
pluginGuid: SortEnumType @cost(weight: "10")
api: SortEnumType @cost(weight: "10")
minmumVersion: SortEnumType @cost(weight: "10")
website: SortEnumType @cost(weight: "10")
helpPage: SortEnumType @cost(weight: "10")
repository: SortEnumType @cost(weight: "10")
requiresAdmin: SortEnumType @cost(weight: "10")
supportsWindows: SortEnumType @cost(weight: "10")
supportsLinux: SortEnumType @cost(weight: "10")
supportsOSX: SortEnumType @cost(weight: "10")
}
input RGBDeviceTypeOperationFilterInput {
eq: RGBDeviceType
in: [RGBDeviceType!]
neq: RGBDeviceType
nin: [RGBDeviceType!]
eq: RGBDeviceType @cost(weight: "10")
neq: RGBDeviceType @cost(weight: "10")
in: [RGBDeviceType!] @cost(weight: "10")
nin: [RGBDeviceType!] @cost(weight: "10")
}
input ReleaseFilterInput {
and: [ReleaseFilterInput!]
or: [ReleaseFilterInput!]
id: LongOperationFilterInput
version: StringOperationFilterInput
changelog: StringOperationFilterInput
createdAt: DateTimeOperationFilterInput
dependencies: ListFilterInputTypeOfEntryFilterInput
downloadSize: LongOperationFilterInput
downloads: LongOperationFilterInput
downloadSize: LongOperationFilterInput
md5Hash: StringOperationFilterInput
entry: EntryFilterInput
entryId: LongOperationFilterInput
id: LongOperationFilterInput
md5Hash: StringOperationFilterInput
or: [ReleaseFilterInput!]
version: StringOperationFilterInput
dependencies: ListFilterInputTypeOfEntryFilterInput
}
input ReleaseSortInput {
changelog: SortEnumType
createdAt: SortEnumType
downloadSize: SortEnumType
downloads: SortEnumType
entry: EntrySortInput
entryId: SortEnumType
id: SortEnumType
md5Hash: SortEnumType
version: SortEnumType
id: SortEnumType @cost(weight: "10")
version: SortEnumType @cost(weight: "10")
changelog: SortEnumType @cost(weight: "10")
createdAt: SortEnumType @cost(weight: "10")
downloads: SortEnumType @cost(weight: "10")
downloadSize: SortEnumType @cost(weight: "10")
md5Hash: SortEnumType @cost(weight: "10")
entry: EntrySortInput @cost(weight: "10")
entryId: SortEnumType @cost(weight: "10")
}
input SetLayoutInfoInput {
@ -574,58 +511,131 @@ input SetLayoutInfoInput {
input StringOperationFilterInput {
and: [StringOperationFilterInput!]
contains: String
endsWith: String
eq: String
in: [String]
ncontains: String
nendsWith: String
neq: String
nin: [String]
nstartsWith: String
or: [StringOperationFilterInput!]
startsWith: String
eq: String @cost(weight: "10")
neq: String @cost(weight: "10")
contains: String @cost(weight: "20")
ncontains: String @cost(weight: "20")
in: [String] @cost(weight: "10")
nin: [String] @cost(weight: "10")
startsWith: String @cost(weight: "20")
nstartsWith: String @cost(weight: "20")
endsWith: String @cost(weight: "20")
nendsWith: String @cost(weight: "20")
}
input TagFilterInput {
and: [TagFilterInput!]
or: [TagFilterInput!]
id: LongOperationFilterInput
name: StringOperationFilterInput
or: [TagFilterInput!]
}
input UpdateEntryImageInput {
description: String
id: UUID!
name: String!
description: String
}
input UpdateEntryInput {
categories: [Long!]!
description: String!
id: Long!
isDefault: Boolean!
name: String!
summary: String!
description: String!
categories: [Long!]!
tags: [String!]!
defaultEntryInfo: DefaultEntryInfoInput
}
input UpdateReleaseInput {
changelog: String
id: Long!
changelog: String
}
input UuidOperationFilterInput {
eq: UUID
gt: UUID
gte: UUID
in: [UUID]
lt: UUID
lte: UUID
neq: UUID
ngt: UUID
ngte: UUID
nin: [UUID]
nlt: UUID
nlte: UUID
eq: UUID @cost(weight: "10")
neq: UUID @cost(weight: "10")
in: [UUID] @cost(weight: "10")
nin: [UUID] @cost(weight: "10")
gt: UUID @cost(weight: "10")
ngt: UUID @cost(weight: "10")
gte: UUID @cost(weight: "10")
ngte: UUID @cost(weight: "10")
lt: UUID @cost(weight: "10")
nlt: UUID @cost(weight: "10")
lte: UUID @cost(weight: "10")
nlte: UUID @cost(weight: "10")
}
"Defines when a policy shall be executed."
enum ApplyPolicy {
"Before the resolver was executed."
BEFORE_RESOLVER
"After the resolver was executed."
AFTER_RESOLVER
"The policy is applied in the validation step before the execution."
VALIDATION
}
enum EntryType {
PLUGIN
PROFILE
LAYOUT
}
enum KeyboardLayoutType {
UNKNOWN
ANSI
ISO
JIS
ABNT
KS
}
enum RGBDeviceType {
NONE
KEYBOARD
MOUSE
HEADSET
MOUSEPAD
LED_STRIPE
LED_MATRIX
MAINBOARD
GRAPHICS_CARD
DRAM
HEADSET_STAND
KEYPAD
FAN
SPEAKER
COOLER
MONITOR
LED_CONTROLLER
GAME_CONTROLLER
UNKNOWN
ALL
}
enum SortEnumType {
ASC
DESC
}
"The authorize directive."
directive @authorize("The name of the authorization policy that determines access to the annotated resource." policy: String "Roles that are allowed to access the annotated resource." roles: [String!] "Defines when when the authorize directive shall be applied.By default the authorize directives are applied during the validation phase." apply: ApplyPolicy! = BEFORE_RESOLVER) repeatable on OBJECT | FIELD_DEFINITION
"The purpose of the `cost` directive is to define a `weight` for GraphQL types, fields, and arguments. Static analysis can use these weights when calculating the overall cost of a query or response."
directive @cost("The `weight` argument defines what value to add to the overall cost for every appearance, or possible appearance, of a type, field, argument, etc." weight: String!) on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM | INPUT_FIELD_DEFINITION
"The purpose of the `@listSize` directive is to either inform the static analysis about the size of returned lists (if that information is statically available), or to point the analysis to where to find that information."
directive @listSize("The `assumedSize` argument can be used to statically define the maximum length of a list returned by a field." assumedSize: Int "The `slicingArguments` argument can be used to define which of the field's arguments with numeric type are slicing arguments, so that their value determines the size of the list returned by that field. It may specify a list of multiple slicing arguments." slicingArguments: [String!] "The `slicingArgumentDefaultValue` argument can be used to define a default value for a slicing argument, which is used if the argument is not present in a query." slicingArgumentDefaultValue: Int "The `sizedFields` argument can be used to define that the value of the `assumedSize` argument or of a slicing argument does not affect the size of a list returned by a field itself, but that of a list returned by one of its sub-fields." sizedFields: [String!] "The `requireOneSlicingArgument` argument can be used to inform the static analysis that it should expect that exactly one of the defined slicing arguments is present in a query. If that is not the case (i.e., if none or multiple slicing arguments are present), the static analysis may throw an error." requireOneSlicingArgument: Boolean! = true) on FIELD_DEFINITION
"The `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions."
directive @specifiedBy("The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types." url: String!) on SCALAR
"The `DateTime` scalar represents an ISO-8601 compliant date time type."
scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time")
"The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1."
scalar Long
scalar UUID @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc4122")

View File

@ -27,6 +27,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Artemis.sln.DotSettings.user = Artemis.sln.DotSettings.user
Directory.Packages.props = Directory.Packages.props
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Storage.Legacy", "Artemis.Storage.Legacy\Artemis.Storage.Legacy.csproj", "{D7B0966D-774A-40E4-9455-00C1261ACB6A}"

View File

@ -0,0 +1,12 @@
<Project>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<!-- If you're a .vbproj user, replace ';' with ',' -->
<DefineConstants>$(DefineConstants);ENABLE_XAML_HOT_RELOAD</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Condition="$(DefineConstants.Contains(ENABLE_XAML_HOT_RELOAD))" Include="Avalonia.Markup.Xaml.Loader" />
<PackageReference Condition="$(DefineConstants.Contains(ENABLE_XAML_HOT_RELOAD))" Include="HotAvalonia" />
<PackageReference Include="HotAvalonia.Extensions" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@ -66,5 +66,9 @@
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.11.0" />
<PackageVersion Include="System.Text.Json" Version="9.0.5" />
<PackageVersion Include="TextMateSharp.Grammars" Version="1.0.68" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.0" />
<PackageVersion Include="HotAvalonia" Version="2.1.0" />
<PackageVersion Include="HotAvalonia.Extensions" Version="2.1.0" />
</ItemGroup>
</Project>