From b351f685f7f32b90fb44128bdc79be077f20c5d7 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 15 Nov 2025 15:07:54 +0100 Subject: [PATCH] Add AXAML hot reload (debug only) Major progress on default entries too I guess lmao --- src/Artemis.Core/Constants.cs | 5 + .../Routing/Router/Router.cs | 8 +- src/Artemis.UI.Windows/App.axaml.cs | 5 +- src/Artemis.UI/Artemis.UI.csproj | 36 - src/Artemis.UI/Screens/Home/HomeView.axaml | 2 +- .../Steps/DefaultEntriesStepView.axaml | 60 +- .../Steps/DefaultEntriesStepViewModel.cs | 125 +- .../Steps/DefaultEntryItemView.axaml | 72 ++ .../Steps/DefaultEntryItemView.axaml.cs | 11 + .../Steps/DefaultEntryItemViewModel.cs | 86 ++ .../Details/EntrySpecificationsView.axaml | 16 +- .../Details/EntrySpecificationsViewModel.cs | 2 + .../Library/SubmissionDetailsViewModel.cs | 31 +- .../Models/SubmissionWizardState.cs | 2 + .../Steps/SpecificationsStepViewModel.cs | 4 + .../Steps/UploadStepViewModel.cs | 9 +- .../Queries/Fragments.graphql | 16 +- .../Queries/GetEntries.graphql | 8 +- .../WorkshopConstants.cs | 8 +- .../graphql.config.yml | 2 +- src/Artemis.WebClient.Workshop/schema.graphql | 1120 +++++++++-------- src/Artemis.sln | 1 + src/Directory.Build.targets | 12 + src/Directory.Packages.props | 4 + 24 files changed, 963 insertions(+), 682 deletions(-) create mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemView.axaml create mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemView.axaml.cs create mode 100644 src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemViewModel.cs create mode 100644 src/Directory.Build.targets diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index dfa7791c5..a05acf32c 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -140,6 +140,11 @@ public static class Constants /// Gets the startup arguments provided to the application /// public static ReadOnlyCollection 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")}; diff --git a/src/Artemis.UI.Shared/Routing/Router/Router.cs b/src/Artemis.UI.Shared/Routing/Router/Router.cs index caa95b3e1..3eae340c9 100644 --- a/src/Artemis.UI.Shared/Routing/Router/Router.cs +++ b/src/Artemis.UI.Shared/Routing/Router/Router.cs @@ -251,7 +251,13 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable if (_previousWindowRoute != null && _currentRouteSubject.Value == "blank") Dispatcher.UIThread.InvokeAsync(async () => await Navigate(_previousWindowRoute, new RouterNavigationOptions {AddToHistory = false, EnableLogging = false})); else if (_currentRouteSubject.Value == null || _currentRouteSubject.Value == "blank") - Dispatcher.UIThread.InvokeAsync(async () => await Navigate("home", new RouterNavigationOptions {AddToHistory = false, EnableLogging = true})); + { + string? startupRoute = Constants.GetStartupRoute(); + if (startupRoute != null) + Dispatcher.UIThread.InvokeAsync(async () => await Navigate(startupRoute, new RouterNavigationOptions {AddToHistory = false, EnableLogging = true})); + else + Dispatcher.UIThread.InvokeAsync(async () => await Navigate("home", new RouterNavigationOptions {AddToHistory = false, EnableLogging = true})); + } } private void MainWindowServiceOnMainWindowClosed(object? sender, EventArgs e) diff --git a/src/Artemis.UI.Windows/App.axaml.cs b/src/Artemis.UI.Windows/App.axaml.cs index 593a9ea2a..824c8a9c4 100644 --- a/src/Artemis.UI.Windows/App.axaml.cs +++ b/src/Artemis.UI.Windows/App.axaml.cs @@ -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(); diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 268327347..bddd938b2 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -35,40 +35,4 @@ - - - - DeviceProviderPickerDialogView.axaml - Code - - - DeviceSelectionDialogView.axaml - Code - - - LayoutListView.axaml - Code - - - LayoutListView.axaml - Code - - - LayoutListView.axaml - Code - - - EntryReleasesView.axaml - Code - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Home/HomeView.axaml b/src/Artemis.UI/Screens/Home/HomeView.axaml index b40dec73b..9af3fb2e5 100644 --- a/src/Artemis.UI/Screens/Home/HomeView.axaml +++ b/src/Artemis.UI/Screens/Home/HomeView.axaml @@ -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."> diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntriesStepView.axaml b/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntriesStepView.axaml index 2317f1da8..7133784ca 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntriesStepView.axaml +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntriesStepView.axaml @@ -7,26 +7,52 @@ x:Class="Artemis.UI.Screens.StartupWizard.Steps.DefaultEntriesStepView" x:DataType="steps:DefaultEntriesStepViewModel"> - - + + + - Installing default plugins and profiles, these will provide you with a basic setup to get started with Artemis. + 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. - - - - - - - - + + Device providers + + + + + + + + - - - - + + Essentials + + + + + + + + + + + Other features + + + + + + + + - - + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntriesStepViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntriesStepViewModel.cs index d9226057a..1d0224a5f 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntriesStepViewModel.cs +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntriesStepViewModel.cs @@ -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 _currentEntryProgress = new(); + private readonly Func _getDefaultEntryItemViewModel; + public ObservableCollection DeviceProviderEntryViewModels { get; } = []; + public ObservableCollection EssentialEntryViewModels { get; } = []; + public ObservableCollection OtherEntryViewModels { get; } = []; - public DefaultEntriesStepViewModel(IWorkshopService workshopService, IDeviceService deviceService, IWorkshopClient client, IWindowService windowService) + public DefaultEntriesStepViewModel(IWorkshopService workshopService, IDeviceService deviceService, IWorkshopClient client, + Func getDefaultEntryItemViewModel) { - _workshopService = workshopService; _client = client; - _windowService = windowService; - _currentEntryProgress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage; + _getDefaultEntryItemViewModel = getDefaultEntryItemViewModel; - Continue = ReactiveCommand.Create(() => Wizard.ChangeScreen()); + ContinueText = "Install selected entries"; + Continue = ReactiveCommand.CreateFromTask(async ct => + { + if (Installed) + Wizard.ChangeScreen(); + 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 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 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 entryViewModels) + { + List 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 result = await _client.GetDefaultEntries.ExecuteAsync(100, null, cancellationToken); List entries = result.Data?.EntriesV2?.Edges?.Select(e => e.Node).Cast().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; - } - 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++; + 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); } - return true; + FetchingDefaultEntries = false; } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemView.axaml b/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemView.axaml new file mode 100644 index 000000000..5c5fc7f8a --- /dev/null +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemView.axaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Install + + + + + Already installed + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemView.axaml.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemView.axaml.cs new file mode 100644 index 000000000..a19ca2816 --- /dev/null +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.StartupWizard.Steps; + +public partial class DefaultEntryItemView : ReactiveUserControl +{ + public DefaultEntryItemView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemViewModel.cs new file mode 100644 index 000000000..3568f3d08 --- /dev/null +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/DefaultEntryItemViewModel.cs @@ -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 _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 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); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsView.axaml index dda4ba091..14790c176 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsView.axaml @@ -63,8 +63,12 @@ - - Download by default (admin only) + + + Download by default (admin only) + Essential + Device provider + @@ -93,10 +97,10 @@ - - - - + + diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsViewModel.cs index bea16889d..4ea3ed070 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsViewModel.cs @@ -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; diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsViewModel.cs index da3216050..bcef82f2c 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsViewModel.cs @@ -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 result = await _client.UpdateEntry.ExecuteAsync(input, cancellationToken); diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Models/SubmissionWizardState.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Models/SubmissionWizardState.cs index 5e6927690..d64e7f691 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Models/SubmissionWizardState.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Models/SubmissionWizardState.cs @@ -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 Categories { get; set; } = new(); public List Tags { get; set; } = new(); diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/SpecificationsStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/SpecificationsStepViewModel.cs index 01a5c19fc..be4f864b9 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/SpecificationsStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/SpecificationsStepViewModel.cs @@ -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(); diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs index c205e24aa..ffb7cdd87 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs @@ -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(); diff --git a/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql b/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql index a1f8eb669..f1c94bfa2 100644 --- a/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql +++ b/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql @@ -6,7 +6,7 @@ fragment category on Category { fragment image on Image { id name - description + description } fragment layoutInfo on LayoutInfo { @@ -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 { @@ -77,7 +82,7 @@ fragment release on Release { downloadSize md5Hash createdAt -} +} fragment releaseDetails on Release { ...release @@ -95,4 +100,9 @@ fragment pluginInfo on PluginInfo { supportsWindows supportsLinux supportsOSX +} + +fragment defaultEntryInfo on DefaultEntryInfo { + isEssential + isDeviceProvider } \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql b/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql index 3827be7ea..782faa984 100644 --- a/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql +++ b/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql @@ -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 diff --git a/src/Artemis.WebClient.Workshop/WorkshopConstants.cs b/src/Artemis.WebClient.Workshop/WorkshopConstants.cs index 10807064c..907ddb843 100644 --- a/src/Artemis.WebClient.Workshop/WorkshopConstants.cs +++ b/src/Artemis.WebClient.Workshop/WorkshopConstants.cs @@ -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"; } \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/graphql.config.yml b/src/Artemis.WebClient.Workshop/graphql.config.yml index 9662a514f..4e6e409e5 100644 --- a/src/Artemis.WebClient.Workshop/graphql.config.yml +++ b/src/Artemis.WebClient.Workshop/graphql.config.yml @@ -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 diff --git a/src/Artemis.WebClient.Workshop/schema.graphql b/src/Artemis.WebClient.Workshop/schema.graphql index f07aef9e7..17190a026 100644 --- a/src/Artemis.WebClient.Workshop/schema.graphql +++ b/src/Artemis.WebClient.Workshop/schema.graphql @@ -1,631 +1,641 @@ # This file was generated. Do not edit manually. schema { - query: Query - mutation: Mutation + query: Query + mutation: Mutation } type Category { - icon: String! - id: Long! - name: String! + id: Long! + name: String! + icon: String! } "Information about the offset pagination." type CollectionSegmentInfo { - "Indicates whether more items exist following the set defined by the clients arguments." - hasNextPage: Boolean! - "Indicates whether more items exist prior the set defined by the clients arguments." - hasPreviousPage: Boolean! + "Indicates whether more items exist following the set defined by the clients arguments." + hasNextPage: Boolean! + "Indicates whether more items exist prior the set defined by the clients arguments." + 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! + "Information to aid in pagination." + pageInfo: CollectionSegmentInfo! + "A flattened list of the items." + items: [Entry!] + totalCount: Int! @cost(weight: "10") } "A connection to a list of items." type EntriesV2Connection { - "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! + "Information to aid in pagination." + pageInfo: PageInfo! + "A list of edges." + edges: [EntriesV2Edge!] + "A flattened list of the nodes." + nodes: [Entry!] + "Identifies the total count of items in the connection." + totalCount: Int! @cost(weight: "10") } "An edge in a connection." type EntriesV2Edge { - "A cursor for use in pagination." - cursor: String! - "The item at the end of the edge." - node: Entry! + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Entry! } type Entry { - author: String! - authorId: UUID! - categories: [Category!]! - createdAt: DateTime! - dependantReleases: [Release!]! - description: String! - downloads: Long! - entryType: EntryType! - icon: Image - iconId: UUID - id: Long! - images: [Image!]! - isDefault: Boolean! - isOfficial: Boolean! - latestRelease: Release - latestReleaseId: Long - layoutInfo: [LayoutInfo!]! - name: String! - pluginInfo: PluginInfo - popularityScore: Float! - releases: [Release!]! - summary: String! - tags: [Tag!]! + id: Long! + entryType: EntryType! + createdAt: DateTime! + authorId: UUID! + author: String! + isOfficial: Boolean! + name: String! + summary: String! + description: String! + downloads: Long! + iconId: UUID + icon: Image + latestReleaseId: Long + latestRelease: Release + pluginInfo: PluginInfo + layoutInfo: [LayoutInfo!]! + defaultEntryInfo: DefaultEntryInfo + categories: [Category!]! + tags: [Tag!]! + images: [Image!]! + releases: [Release!]! + dependantReleases: [Release!]! } type Image { - description: String - entry: Entry - entryId: Long - height: Int! - id: UUID! - mimeType: String! - name: String! - size: Long! - width: Int! + id: UUID! + name: String! + description: String + width: Int! + height: Int! + size: Long! + mimeType: String! + entry: Entry + entryId: Long } type LayoutInfo { - deviceProvider: UUID! - deviceType: RGBDeviceType! - entry: Entry! - entryId: Long! - id: Long! - logicalLayout: String - model: String! - physicalLayout: KeyboardLayoutType - vendor: String! + id: Long! + deviceProvider: UUID! + deviceType: RGBDeviceType! + vendor: String! + model: String! + physicalLayout: KeyboardLayoutType + 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 + "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 - pluginGuid: UUID! - repository: String - requiresAdmin: Boolean! - supportsLinux: Boolean! - supportsOSX: Boolean! - supportsWindows: Boolean! - website: String + entryId: Long! + entry: Entry! + pluginGuid: UUID! + api: Int + minmumVersion: String + website: String + helpPage: String + repository: String + requiresAdmin: Boolean! + supportsWindows: Boolean! + supportsLinux: Boolean! + supportsOSX: Boolean! } "A segment of a collection." type PluginInfosCollectionSegment { - "A flattened list of the items." - items: [PluginInfo!] - "Information to aid in pagination." - pageInfo: CollectionSegmentInfo! - totalCount: Int! + "Information to aid in pagination." + pageInfo: CollectionSegmentInfo! + "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 { - changelog: String - createdAt: DateTime! - dependencies: [Entry!]! - downloadSize: Long! - downloads: Long! - entry: Entry! - entryId: Long! - id: Long! - md5Hash: String - version: String! + id: Long! + version: String! + changelog: String + createdAt: DateTime! + downloads: Long! + downloadSize: Long! + md5Hash: String + entry: Entry! + entryId: Long! + dependencies: [Entry!]! } type Tag { - id: Long! - name: String! + id: Long! + name: String! } +input BooleanOperationFilterInput { + eq: Boolean @cost(weight: "10") + neq: Boolean @cost(weight: "10") +} + +input CategoryFilterInput { + and: [CategoryFilterInput!] + or: [CategoryFilterInput!] + id: LongOperationFilterInput + name: StringOperationFilterInput + icon: StringOperationFilterInput +} + +input CategorySortInput { + id: SortEnumType @cost(weight: "10") + name: SortEnumType @cost(weight: "10") + icon: SortEnumType @cost(weight: "10") +} + +input CreateEntryInput { + entryType: EntryType! + name: String! + summary: String! + description: String! + categories: [Long!]! + tags: [String!]! + defaultEntryInfo: DefaultEntryInfoInput +} + +input CreateLayoutInfoInput { + entryId: Long! + deviceProvider: UUID! + deviceType: RGBDeviceType! + vendor: String! + model: String! + physicalLayout: KeyboardLayoutType + logicalLayout: String +} + +input DateTimeOperationFilterInput { + 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!] + or: [EntryFilterInput!] + id: LongOperationFilterInput + entryType: EntryTypeOperationFilterInput + createdAt: DateTimeOperationFilterInput + authorId: UuidOperationFilterInput + author: StringOperationFilterInput + isOfficial: BooleanOperationFilterInput + name: StringOperationFilterInput + summary: StringOperationFilterInput + description: StringOperationFilterInput + downloads: LongOperationFilterInput + iconId: UuidOperationFilterInput + icon: ImageFilterInput + latestReleaseId: LongOperationFilterInput + latestRelease: ReleaseFilterInput + pluginInfo: PluginInfoFilterInput + layoutInfo: ListFilterInputTypeOfLayoutInfoFilterInput + defaultEntryInfo: DefaultEntryInfoFilterInput + categories: ListFilterInputTypeOfCategoryFilterInput + tags: ListFilterInputTypeOfTagFilterInput + images: ListFilterInputTypeOfImageFilterInput + releases: ListFilterInputTypeOfReleaseFilterInput + dependantReleases: ListFilterInputTypeOfReleaseFilterInput +} + +input EntrySortInput { + 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 @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 +} + +input ImageSortInput { + 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 @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 + vendor: StringOperationFilterInput + model: StringOperationFilterInput + physicalLayout: NullableOfKeyboardLayoutTypeOperationFilterInput + logicalLayout: StringOperationFilterInput + entryId: LongOperationFilterInput + entry: EntryFilterInput +} + +input LayoutInfoInput { + deviceProvider: UUID! + deviceType: RGBDeviceType! + vendor: String! + model: String! + physicalLayout: KeyboardLayoutType + logicalLayout: String +} + +input ListFilterInputTypeOfCategoryFilterInput { + all: CategoryFilterInput @cost(weight: "10") + none: CategoryFilterInput @cost(weight: "10") + some: CategoryFilterInput @cost(weight: "10") + any: Boolean @cost(weight: "10") +} + +input ListFilterInputTypeOfEntryFilterInput { + all: EntryFilterInput @cost(weight: "10") + none: EntryFilterInput @cost(weight: "10") + some: EntryFilterInput @cost(weight: "10") + any: Boolean @cost(weight: "10") +} + +input ListFilterInputTypeOfImageFilterInput { + all: ImageFilterInput @cost(weight: "10") + none: ImageFilterInput @cost(weight: "10") + some: ImageFilterInput @cost(weight: "10") + any: Boolean @cost(weight: "10") +} + +input ListFilterInputTypeOfLayoutInfoFilterInput { + all: LayoutInfoFilterInput @cost(weight: "10") + none: LayoutInfoFilterInput @cost(weight: "10") + some: LayoutInfoFilterInput @cost(weight: "10") + any: Boolean @cost(weight: "10") +} + +input ListFilterInputTypeOfReleaseFilterInput { + all: ReleaseFilterInput @cost(weight: "10") + none: ReleaseFilterInput @cost(weight: "10") + some: ReleaseFilterInput @cost(weight: "10") + any: Boolean @cost(weight: "10") +} + +input ListFilterInputTypeOfTagFilterInput { + all: TagFilterInput @cost(weight: "10") + none: TagFilterInput @cost(weight: "10") + some: TagFilterInput @cost(weight: "10") + any: Boolean @cost(weight: "10") +} + +input LongOperationFilterInput { + 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 @cost(weight: "10") + neq: KeyboardLayoutType @cost(weight: "10") + in: [KeyboardLayoutType] @cost(weight: "10") + nin: [KeyboardLayoutType] @cost(weight: "10") +} + +input PluginInfoFilterInput { + and: [PluginInfoFilterInput!] + 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 +} + +input PluginInfoSortInput { + 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 @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 + downloads: LongOperationFilterInput + downloadSize: LongOperationFilterInput + md5Hash: StringOperationFilterInput + entry: EntryFilterInput + entryId: LongOperationFilterInput + dependencies: ListFilterInputTypeOfEntryFilterInput +} + +input ReleaseSortInput { + 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 { + entryId: Long! + layoutInfo: [LayoutInfoInput!]! +} + +input StringOperationFilterInput { + and: [StringOperationFilterInput!] + or: [StringOperationFilterInput!] + 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 +} + +input UpdateEntryImageInput { + id: UUID! + name: String! + description: String +} + +input UpdateEntryInput { + id: Long! + name: String! + summary: String! + description: String! + categories: [Long!]! + tags: [String!]! + defaultEntryInfo: DefaultEntryInfoInput +} + +input UpdateReleaseInput { + id: Long! + changelog: String +} + +input UuidOperationFilterInput { + 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 { - AFTER_RESOLVER - BEFORE_RESOLVER - VALIDATION + "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 { - LAYOUT - PLUGIN - PROFILE + PLUGIN + PROFILE + LAYOUT } enum KeyboardLayoutType { - ABNT - ANSI - ISO - JIS - KS - UNKNOWN + UNKNOWN + ANSI + ISO + JIS + ABNT + KS } 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 + 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 + 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 +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 - -input BooleanOperationFilterInput { - eq: Boolean - neq: Boolean -} - -input CategoryFilterInput { - and: [CategoryFilterInput!] - icon: StringOperationFilterInput - id: LongOperationFilterInput - name: StringOperationFilterInput - or: [CategoryFilterInput!] -} - -input CategorySortInput { - icon: SortEnumType - id: SortEnumType - name: SortEnumType -} - -input CreateEntryInput { - categories: [Long!]! - description: String! - entryType: EntryType! - isDefault: Boolean! - name: String! - summary: String! - tags: [String!]! -} - -input CreateLayoutInfoInput { - deviceProvider: UUID! - deviceType: RGBDeviceType! - entryId: Long! - logicalLayout: String - model: String! - physicalLayout: KeyboardLayoutType - vendor: 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 -} - -input EntryFilterInput { - and: [EntryFilterInput!] - author: StringOperationFilterInput - authorId: UuidOperationFilterInput - categories: ListFilterInputTypeOfCategoryFilterInput - createdAt: DateTimeOperationFilterInput - dependantReleases: ListFilterInputTypeOfReleaseFilterInput - description: StringOperationFilterInput - downloads: LongOperationFilterInput - entryType: EntryTypeOperationFilterInput - icon: ImageFilterInput - iconId: UuidOperationFilterInput - id: LongOperationFilterInput - images: ListFilterInputTypeOfImageFilterInput - isDefault: BooleanOperationFilterInput - isOfficial: BooleanOperationFilterInput - latestRelease: ReleaseFilterInput - latestReleaseId: LongOperationFilterInput - layoutInfo: ListFilterInputTypeOfLayoutInfoFilterInput - name: StringOperationFilterInput - or: [EntryFilterInput!] - pluginInfo: PluginInfoFilterInput - popularityScore: FloatOperationFilterInput - releases: ListFilterInputTypeOfReleaseFilterInput - summary: StringOperationFilterInput - tags: ListFilterInputTypeOfTagFilterInput -} - -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 -} - -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 -} - -input ImageFilterInput { - and: [ImageFilterInput!] - description: 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 -} - -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 -} - -input LayoutInfoFilterInput { - and: [LayoutInfoFilterInput!] - deviceProvider: UuidOperationFilterInput - deviceType: RGBDeviceTypeOperationFilterInput - entry: EntryFilterInput - entryId: LongOperationFilterInput - id: LongOperationFilterInput - logicalLayout: StringOperationFilterInput - model: StringOperationFilterInput - or: [LayoutInfoFilterInput!] - physicalLayout: NullableOfKeyboardLayoutTypeOperationFilterInput - vendor: StringOperationFilterInput -} - -input LayoutInfoInput { - deviceProvider: UUID! - deviceType: RGBDeviceType! - logicalLayout: String - model: String! - physicalLayout: KeyboardLayoutType - vendor: String! -} - -input ListFilterInputTypeOfCategoryFilterInput { - all: CategoryFilterInput - any: Boolean - none: CategoryFilterInput - some: CategoryFilterInput -} - -input ListFilterInputTypeOfEntryFilterInput { - all: EntryFilterInput - any: Boolean - none: EntryFilterInput - some: EntryFilterInput -} - -input ListFilterInputTypeOfImageFilterInput { - all: ImageFilterInput - any: Boolean - none: ImageFilterInput - some: ImageFilterInput -} - -input ListFilterInputTypeOfLayoutInfoFilterInput { - all: LayoutInfoFilterInput - any: Boolean - none: LayoutInfoFilterInput - some: LayoutInfoFilterInput -} - -input ListFilterInputTypeOfReleaseFilterInput { - all: ReleaseFilterInput - any: Boolean - none: ReleaseFilterInput - some: ReleaseFilterInput -} - -input ListFilterInputTypeOfTagFilterInput { - all: TagFilterInput - any: Boolean - none: TagFilterInput - some: TagFilterInput -} - -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 -} - -input NullableOfKeyboardLayoutTypeOperationFilterInput { - eq: KeyboardLayoutType - in: [KeyboardLayoutType] - neq: KeyboardLayoutType - nin: [KeyboardLayoutType] -} - -input PluginInfoFilterInput { - and: [PluginInfoFilterInput!] - api: IntOperationFilterInput - entry: EntryFilterInput - entryId: LongOperationFilterInput - helpPage: StringOperationFilterInput - minmumVersion: StringOperationFilterInput - or: [PluginInfoFilterInput!] - pluginGuid: UuidOperationFilterInput - repository: StringOperationFilterInput - requiresAdmin: 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 -} - -input RGBDeviceTypeOperationFilterInput { - eq: RGBDeviceType - in: [RGBDeviceType!] - neq: RGBDeviceType - nin: [RGBDeviceType!] -} - -input ReleaseFilterInput { - and: [ReleaseFilterInput!] - changelog: StringOperationFilterInput - createdAt: DateTimeOperationFilterInput - dependencies: ListFilterInputTypeOfEntryFilterInput - downloadSize: LongOperationFilterInput - downloads: LongOperationFilterInput - entry: EntryFilterInput - entryId: LongOperationFilterInput - id: LongOperationFilterInput - md5Hash: StringOperationFilterInput - or: [ReleaseFilterInput!] - version: StringOperationFilterInput -} - -input ReleaseSortInput { - changelog: SortEnumType - createdAt: SortEnumType - downloadSize: SortEnumType - downloads: SortEnumType - entry: EntrySortInput - entryId: SortEnumType - id: SortEnumType - md5Hash: SortEnumType - version: SortEnumType -} - -input SetLayoutInfoInput { - entryId: Long! - layoutInfo: [LayoutInfoInput!]! -} - -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 -} - -input TagFilterInput { - and: [TagFilterInput!] - id: LongOperationFilterInput - name: StringOperationFilterInput - or: [TagFilterInput!] -} - -input UpdateEntryImageInput { - description: String - id: UUID! - name: String! -} - -input UpdateEntryInput { - categories: [Long!]! - description: String! - id: Long! - isDefault: Boolean! - name: String! - summary: String! - tags: [String!]! -} - -input UpdateReleaseInput { - changelog: String - id: Long! -} - -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 -} +scalar UUID @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc4122") \ No newline at end of file diff --git a/src/Artemis.sln b/src/Artemis.sln index 7f7a038f0..6ffd58dcf 100644 --- a/src/Artemis.sln +++ b/src/Artemis.sln @@ -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}" diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 000000000..4c08a9025 --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,12 @@ + + + + $(DefineConstants);ENABLE_XAML_HOT_RELOAD + + + + + + + + \ No newline at end of file diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 0d74712df..96732544e 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -66,5 +66,9 @@ + + + + \ No newline at end of file