From 9c04932afa231bbfe5ee012d62c61155cd94bd84 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 24 Mar 2024 21:29:48 +0100 Subject: [PATCH 01/13] Show multiple releases and improve workshop routing --- src/Artemis.UI/Artemis.UI.csproj | 17 +++++ src/Artemis.UI/Routing/Routes.cs | 2 +- src/Artemis.UI/Screens/Root/RootView.axaml.cs | 2 +- .../Screens/Settings/SettingsView.axaml.cs | 2 +- .../Settings/Tabs/ReleasesTabView.axaml.cs | 18 ++--- .../Entries/Details/EntryInfoViewModel.cs | 5 +- .../Entries/Details/EntryReleasesView.axaml | 72 ++++++++++++++----- .../Entries/Details/EntryReleasesViewModel.cs | 38 +++++++--- .../Workshop/Entries/EntriesView.axaml.cs | 2 +- .../Workshop/Entries/EntriesViewModel.cs | 4 +- .../Workshop/Entries/List/EntryListView.axaml | 57 +++++++++++++++ .../EntryListView.axaml.cs} | 28 +++----- .../Entries/List/EntryListViewModel.cs | 57 +++++++-------- .../Entries/Tabs/LayoutListView.axaml | 67 ----------------- .../Entries/Tabs/LayoutListViewModel.cs | 34 --------- .../Entries/Tabs/PluginListView.axaml | 67 ----------------- .../Entries/Tabs/PluginListView.axaml.cs | 43 ----------- .../Entries/Tabs/PluginListViewModel.cs | 33 --------- .../Entries/Tabs/ProfileListView.axaml | 67 ----------------- .../Entries/Tabs/ProfileListView.axaml.cs | 43 ----------- .../Entries/Tabs/ProfileListViewModel.cs | 33 --------- .../Workshop/Layout/LayoutDetailsView.axaml | 2 +- .../Workshop/Layout/LayoutListView.axaml | 16 +++++ .../Workshop/Layout/LayoutListView.axaml.cs | 25 +++++++ .../Workshop/Layout/LayoutListViewModel.cs | 16 +++++ .../Library/WorkshopLibraryVIew.axaml.cs | 4 +- .../Workshop/Plugins/PluginDetailsView.axaml | 10 ++- .../Plugins/PluginDetailsViewModel.cs | 13 ++-- .../Workshop/Plugins/PluginListView.axaml | 16 +++++ .../Workshop/Plugins/PluginListView.axaml.cs | 24 +++++++ .../Workshop/Plugins/PluginListViewModel.cs | 16 +++++ .../Workshop/Profile/ProfileDetailsView.axaml | 10 ++- .../Workshop/Profile/ProfileListView.axaml | 16 +++++ .../Workshop/Profile/ProfileListView.axaml.cs | 25 +++++++ .../Workshop/Profile/ProfileListViewModel.cs | 16 +++++ .../ReleaseWizardView.axaml.cs | 4 +- .../SubmissionWizardView.axaml.cs | 2 +- src/Artemis.UI/Styles/Markdown.axaml | 5 ++ .../Queries/Fragments.graphql | 6 +- 39 files changed, 419 insertions(+), 498 deletions(-) create mode 100644 src/Artemis.UI/Screens/Workshop/Entries/List/EntryListView.axaml rename src/Artemis.UI/Screens/Workshop/Entries/{Tabs/LayoutListView.axaml.cs => List/EntryListView.axaml.cs} (51%) delete mode 100644 src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml delete mode 100644 src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml delete mode 100644 src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml.cs delete mode 100644 src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml delete mode 100644 src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml.cs delete mode 100644 src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml create mode 100644 src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Layout/LayoutListViewModel.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Plugins/PluginListView.axaml create mode 100644 src/Artemis.UI/Screens/Workshop/Plugins/PluginListView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Plugins/PluginListViewModel.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml create mode 100644 src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Profile/ProfileListViewModel.cs diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index d7bd7aafc..7408b305b 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -44,5 +44,22 @@ DeviceSelectionDialogView.axaml Code + + LayoutListView.axaml + Code + + + LayoutListView.axaml + Code + + + LayoutListView.axaml + Code + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Routing/Routes.cs b/src/Artemis.UI/Routing/Routes.cs index 8973793b0..09a677350 100644 --- a/src/Artemis.UI/Routing/Routes.cs +++ b/src/Artemis.UI/Routing/Routes.cs @@ -7,11 +7,11 @@ using Artemis.UI.Screens.Settings.Updating; using Artemis.UI.Screens.SurfaceEditor; using Artemis.UI.Screens.Workshop; using Artemis.UI.Screens.Workshop.Entries; -using Artemis.UI.Screens.Workshop.Entries.Tabs; using Artemis.UI.Screens.Workshop.Home; using Artemis.UI.Screens.Workshop.Layout; using Artemis.UI.Screens.Workshop.Library; using Artemis.UI.Screens.Workshop.Library.Tabs; +using Artemis.UI.Screens.Workshop.Plugins; using Artemis.UI.Screens.Workshop.Profile; using Artemis.UI.Shared.Routing; using PluginDetailsViewModel = Artemis.UI.Screens.Workshop.Plugins.PluginDetailsViewModel; diff --git a/src/Artemis.UI/Screens/Root/RootView.axaml.cs b/src/Artemis.UI/Screens/Root/RootView.axaml.cs index 04acf1892..90cd6afb8 100644 --- a/src/Artemis.UI/Screens/Root/RootView.axaml.cs +++ b/src/Artemis.UI/Screens/Root/RootView.axaml.cs @@ -19,7 +19,7 @@ public partial class RootView : ReactiveUserControl { try { - Dispatcher.UIThread.Invoke(() => RootFrame.NavigateFromObject(viewModel)); + RootFrame.NavigateFromObject(viewModel); } catch (Exception) { diff --git a/src/Artemis.UI/Screens/Settings/SettingsView.axaml.cs b/src/Artemis.UI/Screens/Settings/SettingsView.axaml.cs index 6e04c8c28..cb03c7a44 100644 --- a/src/Artemis.UI/Screens/Settings/SettingsView.axaml.cs +++ b/src/Artemis.UI/Screens/Settings/SettingsView.axaml.cs @@ -18,7 +18,7 @@ public partial class SettingsView : ReactiveUserControl private void Navigate(ViewModelBase viewModel) { - Dispatcher.UIThread.Invoke(() => TabFrame.NavigateFromObject(viewModel)); + TabFrame.NavigateFromObject(viewModel); } private void NavigationView_OnBackRequested(object? sender, NavigationViewBackRequestedEventArgs e) diff --git a/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabView.axaml.cs b/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabView.axaml.cs index cebc6f402..aadd8b4a6 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabView.axaml.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/ReleasesTabView.axaml.cs @@ -17,17 +17,13 @@ public partial class ReleasesTabView : ReactiveUserControl private void Navigate(ViewModelBase viewModel) { - Dispatcher.UIThread.Invoke(() => + try { - try - { - ReleaseFrame.NavigateFromObject(viewModel); - } - catch (Exception) - { - // ignored - } - }); + ReleaseFrame.NavigateFromObject(viewModel); + } + catch (Exception) + { + // ignored + } } - } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoViewModel.cs index d5c4de433..cd37606ea 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Artemis.Core; using Artemis.UI.Shared; @@ -12,12 +13,12 @@ public class EntryInfoViewModel : ViewModelBase private readonly INotificationService _notificationService; public IEntryDetails Entry { get; } public DateTimeOffset? UpdatedAt { get; } - + public EntryInfoViewModel(IEntryDetails entry, INotificationService notificationService) { _notificationService = notificationService; Entry = entry; - UpdatedAt = Entry.LatestRelease?.CreatedAt ?? Entry.CreatedAt; + UpdatedAt = Entry.Releases.Any() ? Entry.Releases.Max(r => r.CreatedAt) : Entry.CreatedAt; } public async Task CopyShareLink() diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesView.axaml index 78944e621..5453ac531 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesView.axaml @@ -6,6 +6,7 @@ xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" + xmlns:workshop="clr-namespace:Artemis.WebClient.Workshop;assembly=Artemis.WebClient.Workshop" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryReleasesView" x:DataType="details:EntryReleasesViewModel"> @@ -14,39 +15,74 @@ - Latest release + Releases - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesViewModel.cs index 12bd22b3a..8d35a9f38 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesViewModel.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Reactive; using System.Threading; using System.Threading.Tasks; using Artemis.UI.Shared; +using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Utilities; @@ -19,34 +22,49 @@ public class EntryReleasesViewModel : ViewModelBase private readonly EntryInstallationHandlerFactory _factory; private readonly IWindowService _windowService; private readonly INotificationService _notificationService; + private readonly IRouter _router; - public EntryReleasesViewModel(IEntryDetails entry, EntryInstallationHandlerFactory factory, IWindowService windowService, INotificationService notificationService) + public EntryReleasesViewModel(IEntryDetails entry, EntryInstallationHandlerFactory factory, IWindowService windowService, INotificationService notificationService, IRouter router) { _factory = factory; _windowService = windowService; _notificationService = notificationService; + _router = router; Entry = entry; + LatestRelease = Entry.Releases.MaxBy(r => r.CreatedAt); + OtherReleases = Entry.Releases.OrderByDescending(r => r.CreatedAt).Skip(1).Take(4).Cast().ToList(); + DownloadLatestRelease = ReactiveCommand.CreateFromTask(ExecuteDownloadLatestRelease); OnInstallationStarted = Confirm; + NavigateToRelease = ReactiveCommand.CreateFromTask(ExecuteNavigateToRelease); } public IEntryDetails Entry { get; } - public ReactiveCommand DownloadLatestRelease { get; } + public IRelease? LatestRelease { get; } + public List OtherReleases { get; } - public Func> OnInstallationStarted { get; set; } + public ReactiveCommand DownloadLatestRelease { get; } + public ReactiveCommand NavigateToRelease { get; } + + public Func> OnInstallationStarted { get; set; } public Func? OnInstallationFinished { get; set; } + private async Task ExecuteNavigateToRelease(IRelease release) + { + await _router.Navigate($"workshop/entries/{Entry.Id}/releases/{release.Id}"); + } + private async Task ExecuteDownloadLatestRelease(CancellationToken cancellationToken) { - if (Entry.LatestRelease == null) + if (LatestRelease == null) return; - - if (await OnInstallationStarted(Entry)) + + if (await OnInstallationStarted(Entry, LatestRelease)) return; IEntryInstallationHandler installationHandler = _factory.CreateHandler(Entry.EntryType); - EntryInstallResult result = await installationHandler.InstallAsync(Entry, Entry.LatestRelease, new Progress(), cancellationToken); + EntryInstallResult result = await installationHandler.InstallAsync(Entry, LatestRelease, new Progress(), cancellationToken); if (result.IsSuccess && result.Entry != null) { if (OnInstallationFinished != null) @@ -62,13 +80,13 @@ public class EntryReleasesViewModel : ViewModelBase } } - private async Task Confirm(IEntryDetails entryDetails) + private async Task Confirm(IEntryDetails entryDetails, IRelease release) { bool confirm = await _windowService.ShowConfirmContentDialog( "Install latest release", - $"Are you sure you want to download and install version {entryDetails.LatestRelease?.Version} of {entryDetails.Name}?" + $"Are you sure you want to download and install version {release.Version} of {entryDetails.Name}?" ); - + return !confirm; } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntriesView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntriesView.axaml.cs index 3f2106645..1299973d2 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/EntriesView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntriesView.axaml.cs @@ -18,7 +18,7 @@ public partial class EntriesView : ReactiveUserControl private void Navigate(ViewModelBase viewModel) { - Dispatcher.UIThread.Invoke(() => TabFrame.NavigateFromObject(viewModel)); + TabFrame.NavigateFromObject(viewModel); } private void NavigationView_OnBackRequested(object? sender, NavigationViewBackRequestedEventArgs e) diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs index e76014c34..760193832 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs @@ -53,8 +53,8 @@ public partial class EntriesViewModel : RoutableHostScreen public void GoBack() { - if (ViewingDetails) - _router.GoBack(); + if (ViewingDetails && SelectedTab != null) + _router.Navigate(SelectedTab.Path); else _router.Navigate("workshop"); } diff --git a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListView.axaml new file mode 100644 index 000000000..597282d98 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListView.axaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + Categories + + + + + + + + + + + + + + + + + + + + + + + + + + + Looks like your current filters gave no results + + Modify or clear your filters to view other entries + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListView.axaml.cs similarity index 51% rename from src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml.cs rename to src/Artemis.UI/Screens/Workshop/Entries/List/EntryListView.axaml.cs index a003f47bb..e4a15ed77 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListView.axaml.cs @@ -1,38 +1,30 @@ -using System; -using System.Reactive.Disposables; using System.Threading; -using Artemis.UI.Shared.Routing; using Avalonia.Controls; using Avalonia.ReactiveUI; -using Avalonia.Threading; using ReactiveUI; -namespace Artemis.UI.Screens.Workshop.Entries.Tabs; +namespace Artemis.UI.Screens.Workshop.Entries.List; -public partial class LayoutListView : ReactiveUserControl +public partial class EntryListView : ReactiveUserControl { - public LayoutListView() + public EntryListView() { InitializeComponent(); EntriesScrollViewer.SizeChanged += (_, _) => UpdateEntriesPerFetch(); - - this.WhenActivated(d => - { - UpdateEntriesPerFetch(); - ViewModel.WhenAnyValue(vm => vm.Screen).WhereNotNull().Subscribe(Navigate).DisposeWith(d); - }); + + this.WhenActivated(_ => UpdateEntriesPerFetch()); } private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e) { + if (ViewModel == null) + return; + // When near the bottom of EntriesScrollViewer, call FetchMore on the view model if (EntriesScrollViewer.Offset.Y != 0 && EntriesScrollViewer.Extent.Height - (EntriesScrollViewer.Viewport.Height + EntriesScrollViewer.Offset.Y) < 100) - ViewModel?.FetchMore(CancellationToken.None); - } + ViewModel.FetchMore(CancellationToken.None); - private void Navigate(RoutableScreen viewModel) - { - Dispatcher.UIThread.Invoke(() => RouterFrame.NavigateFromObject(viewModel), DispatcherPriority.ApplicationIdle); + ViewModel.ScrollOffset = EntriesScrollViewer.Offset; } private void UpdateEntriesPerFetch() diff --git a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs index 92322920d..f335c9128 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListViewModel.cs @@ -6,6 +6,7 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; +using Artemis.UI.Extensions; using Artemis.UI.Screens.Workshop.Categories; using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Services; @@ -15,29 +16,28 @@ using DynamicData; using PropertyChanged.SourceGenerator; using ReactiveUI; using StrawberryShake; +using Vector = Avalonia.Vector; namespace Artemis.UI.Screens.Workshop.Entries.List; -public abstract partial class EntryListViewModel : RoutableHostScreen +public partial class EntryListViewModel : RoutableScreen { private readonly SourceList _entries = new(); private readonly INotificationService _notificationService; private readonly IWorkshopClient _workshopClient; - private readonly string _route; private IGetEntriesv2_EntriesV2_PageInfo? _currentPageInfo; [Notify] private bool _initializing = true; [Notify] private bool _fetchingMore; [Notify] private int _entriesPerFetch; + [Notify] private Vector _scrollOffset; - protected EntryListViewModel(string route, - IWorkshopClient workshopClient, + protected EntryListViewModel(IWorkshopClient workshopClient, CategoriesViewModel categoriesViewModel, EntryListInputViewModel entryListInputViewModel, INotificationService notificationService, Func getEntryListViewModel) { - _route = route; _workshopClient = workshopClient; _notificationService = notificationService; @@ -50,37 +50,31 @@ public abstract partial class EntryListViewModel : RoutableHostScreen { - // Respond to filter query input changes InputViewModel.WhenAnyValue(vm => vm.Search).Skip(1).Throttle(TimeSpan.FromMilliseconds(200)).Subscribe(_ => Reset()).DisposeWith(d); CategoriesViewModel.WhenAnyValue(vm => vm.CategoryFilters).Skip(1).Subscribe(_ => Reset()).DisposeWith(d); }); + + // Load entries when the view model is first activated + this.WhenActivatedAsync(async _ => + { + if (_entries.Count == 0) + { + await Task.Delay(250); + await FetchMore(CancellationToken.None); + Initializing = false; + } + }); } public CategoriesViewModel CategoriesViewModel { get; } public EntryListInputViewModel InputViewModel { get; } + public EntryType? EntryType { get; set; } public ReadOnlyObservableCollection Entries { get; } - - public override async Task OnNavigating(NavigationArguments args, CancellationToken cancellationToken) - { - if (_entries.Count == 0) - { - await Task.Delay(250, cancellationToken); - await FetchMore(cancellationToken); - Initializing = false; - } - } - - public override Task OnClosing(NavigationArguments args) - { - // Clear search if not navigating to a child - if (!args.Path.StartsWith(_route)) - InputViewModel.ClearLastSearch(); - return base.OnClosing(args); - } - + public async Task FetchMore(CancellationToken cancellationToken) { if (FetchingMore || _currentPageInfo != null && !_currentPageInfo.HasNextPage) @@ -119,12 +113,19 @@ public abstract partial class EntryListViewModel : RoutableHostScreen GetSort() + private IReadOnlyList GetSort() { // Sort by created at if (InputViewModel.SortBy == 1) diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml deleted file mode 100644 index 1c0c4182c..000000000 --- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - Categories - - - - - - - - - - - - - - - - - - - - - - - - - - - Looks like your current filters gave no results - - Modify or clear your filters to view other device layouts - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs deleted file mode 100644 index 11c97846c..000000000 --- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using Artemis.UI.Screens.Workshop.Categories; -using Artemis.UI.Screens.Workshop.Entries.List; -using Artemis.UI.Shared.Routing; -using Artemis.UI.Shared.Services; -using Artemis.WebClient.Workshop; - -namespace Artemis.UI.Screens.Workshop.Entries.Tabs; - -public class LayoutListViewModel : List.EntryListViewModel -{ - public LayoutListViewModel(IWorkshopClient workshopClient, - CategoriesViewModel categoriesViewModel, - EntryListInputViewModel entryListInputViewModel, - INotificationService notificationService, - Func getEntryListViewModel) - : base("workshop/entries/layouts", workshopClient, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel) - { - entryListInputViewModel.SearchWatermark = "Search layouts"; - } - - protected override EntryFilterInput GetFilter() - { - return new EntryFilterInput - { - And = new[] - { - new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = EntryType.Layout}}, - new EntryFilterInput(){LatestReleaseId = new LongOperationFilterInput {Gt = 0}}, - base.GetFilter() - } - }; - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml deleted file mode 100644 index 7b6cb1f0c..000000000 --- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - Categories - - - - - - - - - - - - - - - - - - - - - - - - - - - Looks like your current filters gave no results - - Modify or clear your filters to view other plugins - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml.cs deleted file mode 100644 index 8cfaa1696..000000000 --- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListView.axaml.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Reactive.Disposables; -using System.Threading; -using Artemis.UI.Shared.Routing; -using Avalonia.Controls; -using Avalonia.ReactiveUI; -using Avalonia.Threading; -using ReactiveUI; - -namespace Artemis.UI.Screens.Workshop.Entries.Tabs; - -public partial class PluginListView : ReactiveUserControl -{ - public PluginListView() - { - InitializeComponent(); - EntriesScrollViewer.SizeChanged += (_, _) => UpdateEntriesPerFetch(); - - this.WhenActivated(d => - { - UpdateEntriesPerFetch(); - ViewModel.WhenAnyValue(vm => vm.Screen).WhereNotNull().Subscribe(Navigate).DisposeWith(d); - }); - } - - private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e) - { - // When near the bottom of EntriesScrollViewer, call FetchMore on the view model - if (EntriesScrollViewer.Offset.Y != 0 && EntriesScrollViewer.Extent.Height - (EntriesScrollViewer.Viewport.Height + EntriesScrollViewer.Offset.Y) < 100) - ViewModel?.FetchMore(CancellationToken.None); - } - - private void Navigate(RoutableScreen viewModel) - { - Dispatcher.UIThread.Invoke(() => RouterFrame.NavigateFromObject(viewModel), DispatcherPriority.ApplicationIdle); - } - - private void UpdateEntriesPerFetch() - { - if (ViewModel != null) - ViewModel.EntriesPerFetch = (int) (EntriesScrollViewer.Viewport.Height / 120); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListViewModel.cs deleted file mode 100644 index c7ea484a6..000000000 --- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/PluginListViewModel.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using Artemis.UI.Screens.Workshop.Categories; -using Artemis.UI.Screens.Workshop.Entries.List; -using Artemis.UI.Shared.Routing; -using Artemis.UI.Shared.Services; -using Artemis.WebClient.Workshop; - -namespace Artemis.UI.Screens.Workshop.Entries.Tabs; - -public class PluginListViewModel : EntryListViewModel -{ - public PluginListViewModel(IWorkshopClient workshopClient, - CategoriesViewModel categoriesViewModel, - EntryListInputViewModel entryListInputViewModel, - INotificationService notificationService, - Func getEntryListViewModel) - : base("workshop/entries/plugins", workshopClient, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel) - { - entryListInputViewModel.SearchWatermark = "Search plugins"; - } - - protected override EntryFilterInput GetFilter() - { - return new EntryFilterInput - { - And = new[] - { - new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = EntryType.Plugin}}, - base.GetFilter() - } - }; - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml deleted file mode 100644 index 03028d4b8..000000000 --- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - Categories - - - - - - - - - - - - - - - - - - - - - - - - - - - Looks like your current filters gave no results - - Modify or clear your filters to view some awesome profiles - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml.cs deleted file mode 100644 index b25ba45f3..000000000 --- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Reactive.Disposables; -using System.Threading; -using Artemis.UI.Shared.Routing; -using Avalonia.Controls; -using Avalonia.ReactiveUI; -using Avalonia.Threading; -using ReactiveUI; - -namespace Artemis.UI.Screens.Workshop.Entries.Tabs; - -public partial class ProfileListView : ReactiveUserControl -{ - public ProfileListView() - { - InitializeComponent(); - EntriesScrollViewer.SizeChanged += (_, _) => UpdateEntriesPerFetch(); - - this.WhenActivated(d => - { - UpdateEntriesPerFetch(); - ViewModel.WhenAnyValue(vm => vm.Screen).WhereNotNull().Subscribe(Navigate).DisposeWith(d); - }); - } - - private void ScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e) - { - // When near the bottom of EntriesScrollViewer, call FetchMore on the view model - if (EntriesScrollViewer.Offset.Y != 0 && EntriesScrollViewer.Extent.Height - (EntriesScrollViewer.Viewport.Height + EntriesScrollViewer.Offset.Y) < 100) - ViewModel?.FetchMore(CancellationToken.None); - } - - private void Navigate(RoutableScreen viewModel) - { - Dispatcher.UIThread.Invoke(() => RouterFrame.NavigateFromObject(viewModel), DispatcherPriority.ApplicationIdle); - } - - private void UpdateEntriesPerFetch() - { - if (ViewModel != null) - ViewModel.EntriesPerFetch = (int) (EntriesScrollViewer.Viewport.Height / 120); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs deleted file mode 100644 index 09ed5410b..000000000 --- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using Artemis.UI.Screens.Workshop.Categories; -using Artemis.UI.Screens.Workshop.Entries.List; -using Artemis.UI.Shared.Routing; -using Artemis.UI.Shared.Services; -using Artemis.WebClient.Workshop; - -namespace Artemis.UI.Screens.Workshop.Entries.Tabs; - -public class ProfileListViewModel : List.EntryListViewModel -{ - public ProfileListViewModel(IWorkshopClient workshopClient, - CategoriesViewModel categoriesViewModel, - EntryListInputViewModel entryListInputViewModel, - INotificationService notificationService, - Func getEntryListViewModel) - : base("workshop/entries/profiles", workshopClient, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel) - { - entryListInputViewModel.SearchWatermark = "Search profiles"; - } - - protected override EntryFilterInput GetFilter() - { - return new EntryFilterInput - { - And = new[] - { - new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = EntryType.Profile}}, - base.GetFilter() - } - }; - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml index 3dd205871..c8ed98401 100644 --- a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml @@ -12,7 +12,7 @@ - + diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml b/src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml new file mode 100644 index 000000000..6c1b6e8ae --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml.cs new file mode 100644 index 000000000..4adafd4f7 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml.cs @@ -0,0 +1,25 @@ +using System; +using System.Reactive.Disposables; +using Artemis.UI.Shared.Routing; +using Avalonia.ReactiveUI; +using Avalonia.Threading; +using ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Layout; + +public partial class LayoutListView : ReactiveUserControl +{ + public LayoutListView() + { + InitializeComponent(); + this.WhenActivated(d => + { + ViewModel.WhenAnyValue(vm => vm.Screen).Subscribe(Navigate).DisposeWith(d); + }); + } + + private void Navigate(RoutableScreen? viewModel) + { + RouterFrame.NavigateFromObject(viewModel ?? ViewModel?.EntryListViewModel); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Layout/LayoutListViewModel.cs new file mode 100644 index 000000000..52806ca6a --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutListViewModel.cs @@ -0,0 +1,16 @@ +using Artemis.UI.Screens.Workshop.Entries.List; +using Artemis.UI.Shared.Routing; +using Artemis.WebClient.Workshop; + +namespace Artemis.UI.Screens.Workshop.Layout; + +public class LayoutListViewModel : RoutableHostScreen +{ + public EntryListViewModel EntryListViewModel { get; } + + public LayoutListViewModel(EntryListViewModel entryListViewModel) + { + EntryListViewModel = entryListViewModel; + EntryListViewModel.EntryType = EntryType.Layout; + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/WorkshopLibraryVIew.axaml.cs b/src/Artemis.UI/Screens/Workshop/Library/WorkshopLibraryVIew.axaml.cs index 7758b5729..5fa303000 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/WorkshopLibraryVIew.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/WorkshopLibraryVIew.axaml.cs @@ -18,9 +18,9 @@ public partial class WorkshopLibraryView : ReactiveUserControl TabFrame.NavigateFromObject(viewModel)); + TabFrame.NavigateFromObject(viewModel); } - + private void NavigationView_OnBackRequested(object? sender, NavigationViewBackRequestedEventArgs e) { ViewModel?.GoBack(); diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsView.axaml b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsView.axaml index dcbb4f5d1..1aa45690d 100644 --- a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsView.axaml @@ -29,7 +29,7 @@ - + @@ -49,7 +49,13 @@ Used by these profiles - + + + + + + + diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs index 64b556639..9a2d94314 100644 --- a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs @@ -33,7 +33,7 @@ public partial class PluginDetailsViewModel : RoutableScreen? _dependants; - + public PluginDetailsViewModel(IWorkshopClient client, IWindowService windowService, IPluginManagementService pluginManagementService, @@ -72,18 +72,21 @@ public partial class PluginDetailsViewModel : RoutableScreen? dependants = (await _client.GetDependantEntries.ExecuteAsync(entryId, 0, 25, cancellationToken)).Data?.Entries?.Items; Dependants = dependants != null && dependants.Any() - ? new ReadOnlyObservableCollection(new ObservableCollection(dependants.Select(_getEntryListViewModel))) + ? new ReadOnlyObservableCollection(new ObservableCollection(dependants + .Select(_getEntryListViewModel) + .OrderByDescending(d => d.Entry.Downloads) + .Take(10))) : null; } - private async Task OnInstallationStarted(IEntryDetails entryDetails) + private async Task OnInstallationStarted(IEntryDetails entryDetails, IRelease release) { bool confirm = await _windowService.ShowConfirmContentDialog( "Installing plugin", - $"You are about to install version {entryDetails.LatestRelease?.Version} of {entryDetails.Name}. \r\n\r\n" + + $"You are about to install version {release.Version} of {entryDetails.Name}. \r\n\r\n" + "Plugins are NOT verified by Artemis and could harm your PC, if you have doubts about a plugin please ask on Discord!", "I trust this plugin, install it" ); diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginListView.axaml b/src/Artemis.UI/Screens/Workshop/Plugins/PluginListView.axaml new file mode 100644 index 000000000..da72fd3fe --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginListView.axaml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginListView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Plugins/PluginListView.axaml.cs new file mode 100644 index 000000000..27b52c560 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginListView.axaml.cs @@ -0,0 +1,24 @@ +using System; +using System.Reactive.Disposables; +using Artemis.UI.Shared.Routing; +using Avalonia.ReactiveUI; +using ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Plugins; + +public partial class PluginListView : ReactiveUserControl +{ + public PluginListView() + { + InitializeComponent(); + this.WhenActivated(d => + { + ViewModel.WhenAnyValue(vm => vm.Screen).Subscribe(Navigate).DisposeWith(d); + }); + } + + private void Navigate(RoutableScreen? viewModel) + { + RouterFrame.NavigateFromObject(viewModel ?? ViewModel?.EntryListViewModel); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Plugins/PluginListViewModel.cs new file mode 100644 index 000000000..a5e1216ec --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginListViewModel.cs @@ -0,0 +1,16 @@ +using Artemis.UI.Screens.Workshop.Entries.List; +using Artemis.UI.Shared.Routing; +using Artemis.WebClient.Workshop; + +namespace Artemis.UI.Screens.Workshop.Plugins; + +public class PluginListViewModel : RoutableHostScreen +{ + public EntryListViewModel EntryListViewModel { get; } + + public PluginListViewModel(EntryListViewModel entryListViewModel) + { + EntryListViewModel = entryListViewModel; + EntryListViewModel.EntryType = EntryType.Plugin; + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml index 499a5b978..9220a2b49 100644 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml @@ -12,7 +12,7 @@ - + @@ -32,7 +32,13 @@ Required plugins - + + + + + + + diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml b/src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml new file mode 100644 index 000000000..70ca1997a --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml.cs new file mode 100644 index 000000000..3f038ad94 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml.cs @@ -0,0 +1,25 @@ +using System; +using System.Reactive.Disposables; +using Artemis.UI.Shared.Routing; +using Avalonia.ReactiveUI; +using Avalonia.Threading; +using ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Profile; + +public partial class ProfileListView : ReactiveUserControl +{ + public ProfileListView() + { + InitializeComponent(); + this.WhenActivated(d => + { + ViewModel.WhenAnyValue(vm => vm.Screen).Subscribe(Navigate).DisposeWith(d); + }); + } + + private void Navigate(RoutableScreen? viewModel) + { + RouterFrame.NavigateFromObject(viewModel ?? ViewModel?.EntryListViewModel); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Profile/ProfileListViewModel.cs new file mode 100644 index 000000000..f012bf65c --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileListViewModel.cs @@ -0,0 +1,16 @@ +using Artemis.UI.Screens.Workshop.Entries.List; +using Artemis.UI.Shared.Routing; +using Artemis.WebClient.Workshop; + +namespace Artemis.UI.Screens.Workshop.Profile; + +public class ProfileListViewModel : RoutableHostScreen +{ + public EntryListViewModel EntryListViewModel { get; } + + public ProfileListViewModel(EntryListViewModel entryListViewModel) + { + EntryListViewModel = entryListViewModel; + EntryListViewModel.EntryType = EntryType.Profile; + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardView.axaml.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardView.axaml.cs index 50bbc4ace..ca6667b66 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/ReleaseWizardView.axaml.cs @@ -8,7 +8,7 @@ using ReactiveUI; namespace Artemis.UI.Screens.Workshop.SubmissionWizard; -public partial class ReleaseWizardView: ReactiveAppWindow +public partial class ReleaseWizardView : ReactiveAppWindow { public ReleaseWizardView() { @@ -25,7 +25,7 @@ public partial class ReleaseWizardView: ReactiveAppWindow Frame.NavigateFromObject(viewModel)); + Frame.NavigateFromObject(viewModel); } catch (Exception e) { diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml.cs index 6c7ece92f..377bc17cb 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/SubmissionWizardView.axaml.cs @@ -25,7 +25,7 @@ public partial class SubmissionWizardView : ReactiveAppWindow Frame.NavigateFromObject(viewModel)); + Frame.NavigateFromObject(viewModel); } catch (Exception e) { diff --git a/src/Artemis.UI/Styles/Markdown.axaml b/src/Artemis.UI/Styles/Markdown.axaml index 6f03f8291..9f1bd2470 100644 --- a/src/Artemis.UI/Styles/Markdown.axaml +++ b/src/Artemis.UI/Styles/Markdown.axaml @@ -2,6 +2,11 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:avalonia="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia" xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia"> + + + Test + + diff --git a/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql b/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql index 0325293c5..ea41aa36a 100644 --- a/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql +++ b/src/Artemis.WebClient.Workshop/Queries/Fragments.graphql @@ -54,12 +54,12 @@ fragment entryDetails on Entry { categories { ...category } - latestRelease { - ...release - } images { ...image } + releases { + ...release + } } fragment release on Release { From 257fa8ae0dd2ead935c0a58e3d75c0f1e66dede5 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 24 Mar 2024 21:58:49 +0100 Subject: [PATCH 02/13] Markdown - Tweak inline code and HR styles --- src/Artemis.UI/Styles/Markdown.axaml | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Artemis.UI/Styles/Markdown.axaml b/src/Artemis.UI/Styles/Markdown.axaml index 6f03f8291..c56fb22af 100644 --- a/src/Artemis.UI/Styles/Markdown.axaml +++ b/src/Artemis.UI/Styles/Markdown.axaml @@ -1,7 +1,13 @@ + xmlns:ctxt="clr-namespace:ColorTextBlock.Avalonia;assembly=ColorTextBlock.Avalonia" + xmlns:controls="clr-namespace:Markdown.Avalonia.Controls;assembly=Markdown.Avalonia"> + + + Markdown.Xaml support ```inline code ``` and block code. + + @@ -60,4 +66,21 @@ + + + + \ No newline at end of file From 107b604c86e071671bd9bf0b76310b2d2f7b32e5 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 25 Mar 2024 20:41:15 +0100 Subject: [PATCH 03/13] Linux - Fix crash in keyframes --- .../Panels/ProfileTree/TreeItemViewModel.cs | 24 ++++++++----------- .../Keyframes/TimelineKeyframeViewModel.cs | 7 ++++-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs index c19e68726..c5adf0948 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs @@ -34,7 +34,7 @@ public abstract partial class TreeItemViewModel : ActivatableViewModelBase private RenderProfileElement? _currentProfileElement; private ObservableAsPropertyHelper? _isFocused; private TimeSpan _time; - + [Notify] private bool _canPaste; [Notify] private bool _isExpanded; [Notify] private bool _isFlyoutOpen; @@ -100,7 +100,7 @@ public abstract partial class TreeItemViewModel : ActivatableViewModelBase public ReactiveCommand Paste { get; } public ReactiveCommand Delete { get; } public abstract bool SupportsChildren { get; } - + public async Task ShowBrokenStateExceptions() { if (ProfileElement == null) @@ -117,7 +117,7 @@ public abstract partial class TreeItemViewModel : ActivatableViewModelBase return; } } - + public void InsertElement(TreeItemViewModel elementViewModel, int targetIndex) { if (elementViewModel.Parent == this && Children.IndexOf(elementViewModel) == targetIndex) @@ -239,26 +239,22 @@ public abstract partial class TreeItemViewModel : ActivatableViewModelBase await _windowService.ShowDialogAsync(layer); await ProfileEditorService.SaveProfileAsync(); } - + private void ExecuteApplyAdaptionHints() { if (ProfileElement is not Layer layer) return; - + ProfileEditorService.ExecuteCommand(new ApplyAdaptionHints(layer, _deviceService.EnabledDevices.ToList())); } private async void UpdateCanPaste(bool isFlyoutOpen) { - string[] formats = await Shared.UI.Clipboard.GetFormatsAsync(); - //diogotr7: This can be null on Linux sometimes. I'm not sure why. - if (formats == null!) - { - CanPaste = false; - return; - } - - CanPaste = formats.Contains(ProfileElementExtensions.ClipboardDataFormat); + string[]? formats = await Shared.UI.Clipboard.GetFormatsAsync(); + + // Can be null on some platforms + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + CanPaste = formats != null && formats.Contains(ProfileElementExtensions.ClipboardDataFormat); } private bool GetIsFocused(ProfileEditorFocusMode focusMode, RenderProfileElement? currentProfileElement) diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeViewModel.cs index c568924bc..3c618d0b8 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/Keyframes/TimelineKeyframeViewModel.cs @@ -174,8 +174,11 @@ public partial class TimelineKeyframeViewModel : ActivatableViewModelBase, IT private async void UpdateCanPaste(bool isFlyoutOpen) { - string[] formats = await Shared.UI.Clipboard.GetFormatsAsync(); - CanPaste = formats.Contains("Artemis.Keyframes"); + string[]? formats = await Shared.UI.Clipboard.GetFormatsAsync(); + + // Can be null on some platforms + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + CanPaste = formats != null && formats.Contains("Artemis.Keyframes"); } #endregion From 9e994840f6e3d98893c002b1d3fd44a23b4e10d4 Mon Sep 17 00:00:00 2001 From: RobertBeekman Date: Wed, 27 Mar 2024 21:43:45 +0100 Subject: [PATCH 04/13] Workshop - Added dedicated release page --- .../Routing/Route/RouteRegistration.cs | 11 +++ src/Artemis.UI/Artemis.UI.csproj | 4 + src/Artemis.UI/Routing/Routes.cs | 98 ++++++++----------- .../Updating/ReleaseDetailsView.axaml | 1 - .../EntryReleases/EntryReleaseView.axaml | 92 +++++++++++++++++ .../EntryReleases/EntryReleaseView.axaml.cs | 14 +++ .../EntryReleases/EntryReleaseViewModel.cs | 30 ++++++ .../EntryReleasesView.axaml | 15 +-- .../EntryReleasesView.axaml.cs | 0 .../EntryReleasesViewModel.cs | 15 ++- .../Layout/LayoutDescriptionView.axaml | 19 ++++ .../Layout/LayoutDescriptionView.axaml.cs | 14 +++ .../Layout/LayoutDescriptionViewModel.cs | 10 ++ .../Workshop/Layout/LayoutDetailsView.axaml | 16 ++- .../Layout/LayoutDetailsView.axaml.cs | 6 ++ .../Workshop/Layout/LayoutDetailsViewModel.cs | 11 ++- .../Workshop/Layout/LayoutListView.axaml.cs | 12 +-- .../Parameters/ReleaseDetailParameters.cs | 6 ++ .../Plugins/PluginDescriptionView.axaml | 35 +++++++ .../Plugins/PluginDescriptionView.axaml.cs | 14 +++ .../Plugins/PluginDescriptionViewModel.cs | 41 ++++++++ .../Workshop/Plugins/PluginDetailsView.axaml | 32 ++---- .../Plugins/PluginDetailsView.axaml.cs | 6 ++ .../Plugins/PluginDetailsViewModel.cs | 22 ++--- .../Workshop/Plugins/PluginListView.axaml.cs | 13 +-- .../Profile/ProfileDescriptionView.axaml | 35 +++++++ .../Profile/ProfileDescriptionView.axaml.cs | 14 +++ .../Profile/ProfileDescriptionViewModel.cs | 41 ++++++++ .../Workshop/Profile/ProfileDetailsView.axaml | 33 ++----- .../Profile/ProfileDetailsView.axaml.cs | 6 ++ .../Profile/ProfileDetailsViewModel.cs | 24 +++-- .../Workshop/Profile/ProfileListView.axaml.cs | 14 +-- .../Artemis.WebClient.Workshop.csproj | 3 + .../Queries/GetReleaseById.graphql | 6 ++ .../graphql.config.yml | 2 +- src/Artemis.WebClient.Workshop/schema.graphql | 10 ++ 36 files changed, 535 insertions(+), 190 deletions(-) create mode 100644 src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml create mode 100644 src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseViewModel.cs rename src/Artemis.UI/Screens/Workshop/{Entries/Details => EntryReleases}/EntryReleasesView.axaml (87%) rename src/Artemis.UI/Screens/Workshop/{Entries/Details => EntryReleases}/EntryReleasesView.axaml.cs (100%) rename src/Artemis.UI/Screens/Workshop/{Entries/Details => EntryReleases}/EntryReleasesViewModel.cs (84%) create mode 100644 src/Artemis.UI/Screens/Workshop/Layout/LayoutDescriptionView.axaml create mode 100644 src/Artemis.UI/Screens/Workshop/Layout/LayoutDescriptionView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Layout/LayoutDescriptionViewModel.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Parameters/ReleaseDetailParameters.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Plugins/PluginDescriptionView.axaml create mode 100644 src/Artemis.UI/Screens/Workshop/Plugins/PluginDescriptionView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Plugins/PluginDescriptionViewModel.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionView.axaml create mode 100644 src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionViewModel.cs create mode 100644 src/Artemis.WebClient.Workshop/Queries/GetReleaseById.graphql diff --git a/src/Artemis.UI.Shared/Routing/Route/RouteRegistration.cs b/src/Artemis.UI.Shared/Routing/Route/RouteRegistration.cs index c62437d03..22246b3f0 100644 --- a/src/Artemis.UI.Shared/Routing/Route/RouteRegistration.cs +++ b/src/Artemis.UI.Shared/Routing/Route/RouteRegistration.cs @@ -18,6 +18,17 @@ public class RouteRegistration : IRouterRegistration where TViewMode Route = new Route(path); } + /// + /// Initializes a new instance of the class. + /// + /// The path of the route. + /// The children of the route. + public RouteRegistration(string path, List children) + { + Route = new Route(path); + Children = children; + } + /// public override string ToString() { diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 7408b305b..c52465960 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -56,6 +56,10 @@ LayoutListView.axaml Code + + EntryReleasesView.axaml + Code + diff --git a/src/Artemis.UI/Routing/Routes.cs b/src/Artemis.UI/Routing/Routes.cs index 09a677350..e64c17868 100644 --- a/src/Artemis.UI/Routing/Routes.cs +++ b/src/Artemis.UI/Routing/Routes.cs @@ -7,6 +7,7 @@ using Artemis.UI.Screens.Settings.Updating; using Artemis.UI.Screens.SurfaceEditor; using Artemis.UI.Screens.Workshop; using Artemis.UI.Screens.Workshop.Entries; +using Artemis.UI.Screens.Workshop.EntryReleases; using Artemis.UI.Screens.Workshop.Home; using Artemis.UI.Screens.Workshop.Layout; using Artemis.UI.Screens.Workshop.Library; @@ -16,68 +17,51 @@ using Artemis.UI.Screens.Workshop.Profile; using Artemis.UI.Shared.Routing; using PluginDetailsViewModel = Artemis.UI.Screens.Workshop.Plugins.PluginDetailsViewModel; -namespace Artemis.UI.Routing; - -public static class Routes +namespace Artemis.UI.Routing { - public static List ArtemisRoutes = - [ - new RouteRegistration("blank"), - new RouteRegistration("home"), - new RouteRegistration("workshop") - { - Children = - [ + public static class Routes + { + public static readonly List ArtemisRoutes = + [ + new RouteRegistration("blank"), + new RouteRegistration("home"), + new RouteRegistration("workshop", [ new RouteRegistration("offline/{message:string}"), - new RouteRegistration("entries") - { - Children = - [ - new RouteRegistration("plugins") - { - Children = [new RouteRegistration("details/{entryId:long}")] - }, - new RouteRegistration("profiles") - { - Children = [new RouteRegistration("details/{entryId:long}")] - }, - new RouteRegistration("layouts") - { - Children = [new RouteRegistration("details/{entryId:long}")] - }, - ] - }, - - new RouteRegistration("library") - { - Children = - [ - new RouteRegistration("installed"), - new RouteRegistration("submissions"), - new RouteRegistration("submissions/{entryId:long}") - ] - } - ] - }, - - new RouteRegistration("surface-editor"), - new RouteRegistration("settings") - { - Children = - [ + new RouteRegistration("entries", [ + new RouteRegistration("plugins", [ + new RouteRegistration("details/{entryId:long}", [ + new RouteRegistration("releases/{releaseId:long}") + ]) + ]), + new RouteRegistration("profiles", [ + new RouteRegistration("details/{entryId:long}", [ + new RouteRegistration("releases/{releaseId:long}") + ]) + ]), + new RouteRegistration("layouts", [ + new RouteRegistration("details/{entryId:long}", [ + new RouteRegistration("releases/{releaseId:long}") + ]) + ]) + ]), + new RouteRegistration("library", [ + new RouteRegistration("installed"), + new RouteRegistration("submissions"), + new RouteRegistration("submissions/{entryId:long}") + ]) + ]), + new RouteRegistration("surface-editor"), + new RouteRegistration("settings", [ new RouteRegistration("general"), new RouteRegistration("plugins"), new RouteRegistration("devices"), - new RouteRegistration("releases") - { - Children = [new RouteRegistration("{releaseId:guid}")] - }, - + new RouteRegistration("releases", [ + new RouteRegistration("{releaseId:guid}") + ]), new RouteRegistration("account"), new RouteRegistration("about") - ] - }, - - new RouteRegistration("profile-editor/{profileConfigurationId:guid}") - ]; + ]), + new RouteRegistration("profile-editor/{profileConfigurationId:guid}") + ]; + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Updating/ReleaseDetailsView.axaml b/src/Artemis.UI/Screens/Settings/Updating/ReleaseDetailsView.axaml index ef4b22704..34258aae0 100644 --- a/src/Artemis.UI/Screens/Settings/Updating/ReleaseDetailsView.axaml +++ b/src/Artemis.UI/Screens/Settings/Updating/ReleaseDetailsView.axaml @@ -149,5 +149,4 @@ - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml new file mode 100644 index 000000000..4a44dbaa1 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + Release info + + + + + Release date + + + + + + Version + + + + + + File size + + + + + + + + + + Release notes + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml.cs b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml.cs new file mode 100644 index 000000000..ea7532c70 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.EntryReleases; + +public partial class EntryReleaseView : ReactiveUserControl +{ + public EntryReleaseView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseViewModel.cs b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseViewModel.cs new file mode 100644 index 000000000..d92912c5c --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseViewModel.cs @@ -0,0 +1,30 @@ +using System.Threading; +using System.Threading.Tasks; +using Artemis.UI.Screens.Workshop.Parameters; +using Artemis.UI.Shared.Routing; +using Artemis.WebClient.Workshop; +using PropertyChanged.SourceGenerator; +using StrawberryShake; + +namespace Artemis.UI.Screens.Workshop.EntryReleases; + +public partial class EntryReleaseViewModel : RoutableScreen +{ + private readonly IWorkshopClient _client; + [Notify] private IGetReleaseById_Release? _release; + + public EntryReleaseViewModel(IWorkshopClient client) + { + _client = client; + } + + /// + public override async Task OnNavigating(ReleaseDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken) + { + IOperationResult result = await _client.GetReleaseById.ExecuteAsync(parameters.ReleaseId, cancellationToken); + if (result.IsErrorResult()) + return; + + Release = result.Data?.Release; + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesView.axaml b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml similarity index 87% rename from src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesView.axaml rename to src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml index 5453ac531..899065ec7 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesView.axaml +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml @@ -25,12 +25,7 @@ CommandParameter="{CompiledBinding LatestRelease}"> - - + @@ -62,12 +57,8 @@ Command="{Binding $parent[details:EntryReleasesView].DataContext.NavigateToRelease}" CommandParameter="{CompiledBinding}"> - - + + diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesView.axaml.cs b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml.cs similarity index 100% rename from src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesView.axaml.cs rename to src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml.cs diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesViewModel.cs b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs similarity index 84% rename from src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesViewModel.cs rename to src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs index 8d35a9f38..4ec497e37 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryReleasesViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs @@ -52,7 +52,20 @@ public class EntryReleasesViewModel : ViewModelBase private async Task ExecuteNavigateToRelease(IRelease release) { - await _router.Navigate($"workshop/entries/{Entry.Id}/releases/{release.Id}"); + switch (Entry.EntryType) + { + case EntryType.Profile: + await _router.Navigate($"workshop/entries/profiles/details/{Entry.Id}/releases/{release.Id}"); + break; + case EntryType.Layout: + await _router.Navigate($"workshop/entries/layouts/details/{Entry.Id}/releases/{release.Id}"); + break; + case EntryType.Plugin: + await _router.Navigate($"workshop/entries/plugins/details/{Entry.Id}/releases/{release.Id}"); + break; + default: + throw new ArgumentOutOfRangeException(nameof(Entry.EntryType)); + } } private async Task ExecuteDownloadLatestRelease(CancellationToken cancellationToken) diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDescriptionView.axaml b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDescriptionView.axaml new file mode 100644 index 000000000..e4cf60508 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDescriptionView.axaml @@ -0,0 +1,19 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDescriptionView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDescriptionView.axaml.cs new file mode 100644 index 000000000..5276e422f --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDescriptionView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Layout; + +public partial class LayoutDescriptionView : ReactiveUserControl +{ + public LayoutDescriptionView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDescriptionViewModel.cs b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDescriptionViewModel.cs new file mode 100644 index 000000000..3fb8f678e --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDescriptionViewModel.cs @@ -0,0 +1,10 @@ +using Artemis.UI.Shared.Routing; +using Artemis.WebClient.Workshop; +using PropertyChanged.SourceGenerator; + +namespace Artemis.UI.Screens.Workshop.Layout; + +public partial class LayoutDescriptionViewModel : RoutableScreen +{ + [Notify] private IEntryDetails? _entry; +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml index c8ed98401..6370121be 100644 --- a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml @@ -4,6 +4,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:layout="clr-namespace:Artemis.UI.Screens.Workshop.Layout" xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight" + xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:ui="clr-namespace:Artemis.UI" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800" x:Class="Artemis.UI.Screens.Workshop.Layout.LayoutDetailsView" x:DataType="layout:LayoutDetailsViewModel"> @@ -18,15 +20,11 @@ - - - - - - - - - + + + + + diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml.cs index 57e93b1e1..d8e9ff858 100644 --- a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsView.axaml.cs @@ -1,4 +1,7 @@ +using System; +using System.Reactive.Disposables; using Avalonia.ReactiveUI; +using ReactiveUI; namespace Artemis.UI.Screens.Workshop.Layout; @@ -7,5 +10,8 @@ public partial class LayoutDetailsView : ReactiveUserControl ViewModel.WhenAnyValue(vm => vm.Screen) + .Subscribe(screen => RouterFrame.NavigateFromObject(screen ?? ViewModel?.LayoutDescriptionViewModel)) + .DisposeWith(d)); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs index 9663f5ad4..69853e0f9 100644 --- a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs @@ -19,7 +19,7 @@ using StrawberryShake; namespace Artemis.UI.Screens.Workshop.Layout; -public partial class LayoutDetailsViewModel : RoutableScreen +public partial class LayoutDetailsViewModel : RoutableHostScreen { private readonly IWorkshopClient _client; private readonly IDeviceService _deviceService; @@ -35,10 +35,12 @@ public partial class LayoutDetailsViewModel : RoutableScreen getEntryInfoViewModel, Func getEntryReleasesViewModel, Func getEntryImagesViewModel) { + LayoutDescriptionViewModel = layoutDescriptionViewModel; _client = client; _deviceService = deviceService; _windowService = windowService; @@ -47,9 +49,12 @@ public partial class LayoutDetailsViewModel : RoutableScreen public LayoutListView() { InitializeComponent(); - this.WhenActivated(d => - { - ViewModel.WhenAnyValue(vm => vm.Screen).Subscribe(Navigate).DisposeWith(d); - }); - } - - private void Navigate(RoutableScreen? viewModel) - { - RouterFrame.NavigateFromObject(viewModel ?? ViewModel?.EntryListViewModel); + this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.Screen) + .Subscribe(screen => RouterFrame.NavigateFromObject(screen ?? ViewModel?.EntryListViewModel)) + .DisposeWith(d)); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Parameters/ReleaseDetailParameters.cs b/src/Artemis.UI/Screens/Workshop/Parameters/ReleaseDetailParameters.cs new file mode 100644 index 000000000..252095dad --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Parameters/ReleaseDetailParameters.cs @@ -0,0 +1,6 @@ +namespace Artemis.UI.Screens.Workshop.Parameters; + +public class ReleaseDetailParameters +{ + public long ReleaseId { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDescriptionView.axaml b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDescriptionView.axaml new file mode 100644 index 000000000..24c91abf8 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDescriptionView.axaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + Used by these profiles + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDescriptionView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDescriptionView.axaml.cs new file mode 100644 index 000000000..bffef29b2 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDescriptionView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Plugins; + +public partial class PluginDescriptionView : ReactiveUserControl +{ + public PluginDescriptionView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDescriptionViewModel.cs b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDescriptionViewModel.cs new file mode 100644 index 000000000..0cae2bbcd --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDescriptionViewModel.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Artemis.UI.Screens.Workshop.Entries.List; +using Artemis.UI.Shared.Routing; +using Artemis.WebClient.Workshop; +using PropertyChanged.SourceGenerator; + +namespace Artemis.UI.Screens.Workshop.Plugins; + +public partial class PluginDescriptionViewModel : RoutableScreen +{ + [Notify] private IEntryDetails? _entry; + [Notify] private List? _dependants; + private readonly IWorkshopClient _client; + private readonly Func _getEntryListViewModel; + + public PluginDescriptionViewModel(IWorkshopClient client, Func getEntryListViewModel) + { + _client = client; + _getEntryListViewModel = getEntryListViewModel; + } + + public async Task SetEntry(IEntryDetails? entry, CancellationToken cancellationToken) + { + Entry = entry; + + if (entry != null) + { + IReadOnlyList? dependants = (await _client.GetDependantEntries.ExecuteAsync(entry.Id, 0, 25, cancellationToken)).Data?.Entries?.Items; + Dependants = dependants != null && dependants.Any() ? dependants.Select(_getEntryListViewModel).OrderByDescending(d => d.Entry.Downloads).Take(10).ToList() : null; + } + else + { + Dependants = null; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsView.axaml b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsView.axaml index 1aa45690d..565898da6 100644 --- a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsView.axaml @@ -5,6 +5,8 @@ xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight" xmlns:plugins="clr-namespace:Artemis.UI.Screens.Workshop.Plugins" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" + xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:ui="clr-namespace:Artemis.UI" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Workshop.Plugins.PluginDetailsView" x:DataType="plugins:PluginDetailsViewModel"> @@ -35,31 +37,11 @@ - - - - - - - - - - - - Used by these profiles - - - - - - - - - - - - - + + + + + diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsView.axaml.cs index c50ffbe89..d4461a4b9 100644 --- a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsView.axaml.cs @@ -1,4 +1,7 @@ +using System; +using System.Reactive.Disposables; using Avalonia.ReactiveUI; +using ReactiveUI; namespace Artemis.UI.Screens.Workshop.Plugins; @@ -7,5 +10,8 @@ public partial class PluginDetailsView : ReactiveUserControl ViewModel.WhenAnyValue(vm => vm.Screen) + .Subscribe(screen => RouterFrame.NavigateFromObject(screen ?? ViewModel?.PluginDescriptionViewModel)) + .DisposeWith(d)); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs index 9a2d94314..b2c222fbb 100644 --- a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs @@ -19,7 +19,7 @@ using StrawberryShake; namespace Artemis.UI.Screens.Workshop.Plugins; -public partial class PluginDetailsViewModel : RoutableScreen +public partial class PluginDetailsViewModel : RoutableHostScreen { private readonly IWorkshopClient _client; private readonly IWindowService _windowService; @@ -27,7 +27,6 @@ public partial class PluginDetailsViewModel : RoutableScreen _getEntryInfoViewModel; private readonly Func _getEntryReleasesViewModel; private readonly Func _getEntryImagesViewModel; - private readonly Func _getEntryListViewModel; [Notify] private IGetPluginEntryById_Entry? _entry; [Notify] private EntryInfoViewModel? _entryInfoViewModel; [Notify] private EntryReleasesViewModel? _entryReleasesViewModel; @@ -37,23 +36,26 @@ public partial class PluginDetailsViewModel : RoutableScreen getEntryInfoViewModel, Func getEntryReleasesViewModel, - Func getEntryImagesViewModel, - Func getEntryListViewModel) + Func getEntryImagesViewModel) { + PluginDescriptionViewModel = pluginDescriptionViewModel; _client = client; _windowService = windowService; _pluginManagementService = pluginManagementService; _getEntryInfoViewModel = getEntryInfoViewModel; _getEntryReleasesViewModel = getEntryReleasesViewModel; _getEntryImagesViewModel = getEntryImagesViewModel; - _getEntryListViewModel = getEntryListViewModel; } + public PluginDescriptionViewModel PluginDescriptionViewModel { get; } + public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken) { - await GetEntry(parameters.EntryId, cancellationToken); + if (Entry?.Id != parameters.EntryId) + await GetEntry(parameters.EntryId, cancellationToken); } private async Task GetEntry(long entryId, CancellationToken cancellationToken) @@ -73,13 +75,7 @@ public partial class PluginDetailsViewModel : RoutableScreen? dependants = (await _client.GetDependantEntries.ExecuteAsync(entryId, 0, 25, cancellationToken)).Data?.Entries?.Items; - Dependants = dependants != null && dependants.Any() - ? new ReadOnlyObservableCollection(new ObservableCollection(dependants - .Select(_getEntryListViewModel) - .OrderByDescending(d => d.Entry.Downloads) - .Take(10))) - : null; + await PluginDescriptionViewModel.SetEntry(Entry, cancellationToken); } private async Task OnInstallationStarted(IEntryDetails entryDetails, IRelease release) diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginListView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Plugins/PluginListView.axaml.cs index 27b52c560..ac248f205 100644 --- a/src/Artemis.UI/Screens/Workshop/Plugins/PluginListView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginListView.axaml.cs @@ -1,6 +1,5 @@ using System; using System.Reactive.Disposables; -using Artemis.UI.Shared.Routing; using Avalonia.ReactiveUI; using ReactiveUI; @@ -11,14 +10,8 @@ public partial class PluginListView : ReactiveUserControl public PluginListView() { InitializeComponent(); - this.WhenActivated(d => - { - ViewModel.WhenAnyValue(vm => vm.Screen).Subscribe(Navigate).DisposeWith(d); - }); - } - - private void Navigate(RoutableScreen? viewModel) - { - RouterFrame.NavigateFromObject(viewModel ?? ViewModel?.EntryListViewModel); + this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.Screen) + .Subscribe(screen => RouterFrame.NavigateFromObject(screen ?? ViewModel?.EntryListViewModel)) + .DisposeWith(d)); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionView.axaml b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionView.axaml new file mode 100644 index 000000000..ff076cdb6 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionView.axaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + Required plugins + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionView.axaml.cs new file mode 100644 index 000000000..5fed996a6 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Profile; + +public partial class ProfileDescriptionView : ReactiveUserControl +{ + public ProfileDescriptionView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionViewModel.cs b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionViewModel.cs new file mode 100644 index 000000000..fd16c6cac --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionViewModel.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Artemis.UI.Screens.Workshop.Entries.List; +using Artemis.UI.Shared.Routing; +using Artemis.WebClient.Workshop; +using PropertyChanged.SourceGenerator; + +namespace Artemis.UI.Screens.Workshop.Profile; + +public partial class ProfileDescriptionViewModel : RoutableScreen +{ + private readonly IWorkshopClient _client; + private readonly Func _getEntryListViewModel; + [Notify] private IEntryDetails? _entry; + [Notify] private List? _dependencies; + + public ProfileDescriptionViewModel(IWorkshopClient client, Func getEntryListViewModel) + { + _client = client; + _getEntryListViewModel = getEntryListViewModel; + } + + public async Task SetEntry(IEntryDetails? entry, CancellationToken cancellationToken) + { + Entry = entry; + + if (entry != null) + { + IReadOnlyList? dependencies = (await _client.GetLatestDependencies.ExecuteAsync(entry.Id, cancellationToken)).Data?.Entry?.LatestRelease?.Dependencies; + Dependencies = dependencies != null && dependencies.Any() ? dependencies.Select(_getEntryListViewModel).ToList() : null; + } + else + { + Dependencies = null; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml index 9220a2b49..9e5c41be6 100644 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml @@ -4,6 +4,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:profile="clr-namespace:Artemis.UI.Screens.Workshop.Profile" xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight" + xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:ui="clr-namespace:Artemis.UI" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800" x:Class="Artemis.UI.Screens.Workshop.Profile.ProfileDetailsView" x:DataType="profile:ProfileDetailsViewModel"> @@ -18,33 +20,12 @@ - - - - - - - - - - - - Required plugins - - - - - - - - - - - - - + + + + + - diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml.cs index 1150bd94c..7a1898ac4 100644 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml.cs @@ -1,4 +1,7 @@ +using System; +using System.Reactive.Disposables; using Avalonia.ReactiveUI; +using ReactiveUI; namespace Artemis.UI.Screens.Workshop.Profile; @@ -7,5 +10,8 @@ public partial class ProfileDetailsView : ReactiveUserControl ViewModel.WhenAnyValue(vm => vm.Screen) + .Subscribe(screen => RouterFrame.NavigateFromObject(screen ?? ViewModel?.ProfileDescriptionViewModel)) + .DisposeWith(d)); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs index 8aadc53d4..1c26abb8d 100644 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Artemis.UI.Screens.Workshop.Entries.Details; -using Artemis.UI.Screens.Workshop.Entries.List; using Artemis.UI.Screens.Workshop.Parameters; using Artemis.UI.Shared.Routing; using Artemis.WebClient.Workshop; @@ -14,36 +13,38 @@ using StrawberryShake; namespace Artemis.UI.Screens.Workshop.Profile; -public partial class ProfileDetailsViewModel : RoutableScreen +public partial class ProfileDetailsViewModel : RoutableHostScreen { private readonly IWorkshopClient _client; private readonly Func _getEntryInfoViewModel; private readonly Func _getEntryReleasesViewModel; private readonly Func _getEntryImagesViewModel; - private readonly Func _getEntryListViewModel; [Notify] private IEntryDetails? _entry; [Notify] private EntryInfoViewModel? _entryInfoViewModel; [Notify] private EntryReleasesViewModel? _entryReleasesViewModel; [Notify] private EntryImagesViewModel? _entryImagesViewModel; - [Notify] private ReadOnlyObservableCollection? _dependencies; public ProfileDetailsViewModel(IWorkshopClient client, + ProfileDescriptionViewModel profileDescriptionViewModel, Func getEntryInfoViewModel, Func getEntryReleasesViewModel, - Func getEntryImagesViewModel, - Func getEntryListViewModel) + Func getEntryImagesViewModel) { + ProfileDescriptionViewModel = profileDescriptionViewModel; + _client = client; _getEntryInfoViewModel = getEntryInfoViewModel; _getEntryReleasesViewModel = getEntryReleasesViewModel; _getEntryImagesViewModel = getEntryImagesViewModel; - _getEntryListViewModel = getEntryListViewModel; } + public ProfileDescriptionViewModel ProfileDescriptionViewModel { get; } + public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken) { - await GetEntry(parameters.EntryId, cancellationToken); + if (Entry?.Id != parameters.EntryId) + await GetEntry(parameters.EntryId, cancellationToken); } private async Task GetEntry(long entryId, CancellationToken cancellationToken) @@ -56,10 +57,7 @@ public partial class ProfileDetailsViewModel : RoutableScreen? dependencies = (await _client.GetLatestDependencies.ExecuteAsync(entryId, cancellationToken)).Data?.Entry?.LatestRelease?.Dependencies; - Dependencies = dependencies != null && dependencies.Any() - ? new ReadOnlyObservableCollection(new ObservableCollection(dependencies.Select(_getEntryListViewModel))) - : null; + + await ProfileDescriptionViewModel.SetEntry(Entry, cancellationToken); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml.cs index 3f038ad94..637a255a6 100644 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml.cs @@ -1,8 +1,6 @@ using System; using System.Reactive.Disposables; -using Artemis.UI.Shared.Routing; using Avalonia.ReactiveUI; -using Avalonia.Threading; using ReactiveUI; namespace Artemis.UI.Screens.Workshop.Profile; @@ -12,14 +10,8 @@ public partial class ProfileListView : ReactiveUserControl public ProfileListView() { InitializeComponent(); - this.WhenActivated(d => - { - ViewModel.WhenAnyValue(vm => vm.Screen).Subscribe(Navigate).DisposeWith(d); - }); - } - - private void Navigate(RoutableScreen? viewModel) - { - RouterFrame.NavigateFromObject(viewModel ?? ViewModel?.EntryListViewModel); + this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.Screen) + .Subscribe(screen => RouterFrame.NavigateFromObject(screen ?? ViewModel?.EntryListViewModel)) + .DisposeWith(d)); } } \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj b/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj index 3e348ce4e..a51abe913 100644 --- a/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj +++ b/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj @@ -48,6 +48,9 @@ MSBuild:GenerateGraphQLCode + + MSBuild:GenerateGraphQLCode + diff --git a/src/Artemis.WebClient.Workshop/Queries/GetReleaseById.graphql b/src/Artemis.WebClient.Workshop/Queries/GetReleaseById.graphql new file mode 100644 index 000000000..4b9aaa912 --- /dev/null +++ b/src/Artemis.WebClient.Workshop/Queries/GetReleaseById.graphql @@ -0,0 +1,6 @@ +query GetReleaseById($id: Long!) { + release(id: $id) { + ...release + changelog + } +} \ 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 a8ba99703..9662a514f 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://localhost:7281/graphql + url: https://workshop.artemis-rgb.com/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 442923f3d..57c872355 100644 --- a/src/Artemis.WebClient.Workshop/schema.graphql +++ b/src/Artemis.WebClient.Workshop/schema.graphql @@ -102,6 +102,7 @@ type Mutation { removeLayoutInfo(id: Long!): LayoutInfo! updateEntry(input: UpdateEntryInput!): Entry updateEntryImage(input: UpdateEntryImageInput!): Image + updateRelease(input: UpdateReleaseInput!): Release } "Information about pagination in a connection." @@ -158,6 +159,7 @@ type Query { entry(id: Long!): Entry pluginInfo(pluginGuid: UUID!): PluginInfo pluginInfos(order: [PluginInfoSortInput!], skip: Int, take: Int, where: PluginInfoFilterInput): PluginInfosCollectionSegment + 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 @@ -165,6 +167,7 @@ type Query { } type Release { + changelog: String createdAt: DateTime! dependencies: [Entry!]! downloadSize: Long! @@ -498,6 +501,7 @@ input RGBDeviceTypeOperationFilterInput { input ReleaseFilterInput { and: [ReleaseFilterInput!] + changelog: StringOperationFilterInput createdAt: DateTimeOperationFilterInput dependencies: ListFilterInputTypeOfEntryFilterInput downloadSize: LongOperationFilterInput @@ -511,6 +515,7 @@ input ReleaseFilterInput { } input ReleaseSortInput { + changelog: SortEnumType createdAt: SortEnumType downloadSize: SortEnumType downloads: SortEnumType @@ -558,6 +563,11 @@ input UpdateEntryInput { tags: [String!]! } +input UpdateReleaseInput { + changelog: String + id: Long! +} + input UuidOperationFilterInput { eq: UUID gt: UUID From d6f1ba9aadb2c36864f336f67c479779a96e3c24 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 29 Mar 2024 10:27:34 +0100 Subject: [PATCH 05/13] Release page - Added release installation --- .../Routing/Router/IRouter.cs | 6 ++ .../Routing/Router/Router.cs | 22 +++++ .../Updating/ReleaseDetailsView.axaml | 2 +- .../EntryReleases/EntryReleaseView.axaml | 67 +++++++++++---- .../EntryReleases/EntryReleaseViewModel.cs | 79 ++++++++++++++++-- .../EntryReleases/EntryReleasesView.axaml | 76 +++++------------ .../EntryReleases/EntryReleasesView.axaml.cs | 7 +- .../EntryReleases/EntryReleasesViewModel.cs | 81 +++++-------------- .../Workshop/Layout/LayoutDetailsViewModel.cs | 5 +- .../Plugins/PluginDetailsViewModel.cs | 5 +- .../Profile/ProfileDetailsViewModel.cs | 6 +- .../IEntryInstallationHandler.cs | 2 +- .../LayoutEntryInstallationHandler.cs | 2 +- .../PluginEntryInstallationHandler.cs | 2 +- .../ProfileEntryInstallationHandler.cs | 2 +- .../Models/InstalledEntry.cs | 2 +- .../Queries/GetReleaseById.graphql | 3 + 17 files changed, 219 insertions(+), 150 deletions(-) diff --git a/src/Artemis.UI.Shared/Routing/Router/IRouter.cs b/src/Artemis.UI.Shared/Routing/Router/IRouter.cs index e23be6eae..2901fdbaf 100644 --- a/src/Artemis.UI.Shared/Routing/Router/IRouter.cs +++ b/src/Artemis.UI.Shared/Routing/Router/IRouter.cs @@ -45,6 +45,12 @@ public interface IRouter /// /// A task containing a boolean value which indicates whether there was a forward path to go back to. Task GoForward(); + + /// + /// Asynchronously navigates upwards to the parent route. + /// + /// + Task GoUp(); /// /// Clears the navigation history. diff --git a/src/Artemis.UI.Shared/Routing/Router/Router.cs b/src/Artemis.UI.Shared/Routing/Router/Router.cs index 39e067ec5..3f1263a29 100644 --- a/src/Artemis.UI.Shared/Routing/Router/Router.cs +++ b/src/Artemis.UI.Shared/Routing/Router/Router.cs @@ -161,6 +161,28 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable return true; } + /// + public async Task GoUp() + { + string? currentPath = _currentRouteSubject.Value; + + // Keep removing segments until we find a parent route that resolves + while (currentPath != null && currentPath.Contains('/')) + { + string parentPath = currentPath[..currentPath.LastIndexOf('/')]; + RouteResolution resolution = Resolve(parentPath); + if (resolution.Success) + { + await Navigate(parentPath, new RouterNavigationOptions {AddToHistory = false}); + return true; + } + + currentPath = parentPath; + } + + return false; + } + /// public void ClearHistory() { diff --git a/src/Artemis.UI/Screens/Settings/Updating/ReleaseDetailsView.axaml b/src/Artemis.UI/Screens/Settings/Updating/ReleaseDetailsView.axaml index 34258aae0..cdb9738aa 100644 --- a/src/Artemis.UI/Screens/Settings/Updating/ReleaseDetailsView.axaml +++ b/src/Artemis.UI/Screens/Settings/Updating/ReleaseDetailsView.axaml @@ -124,7 +124,7 @@ - + File size - + - Release info + + + Release info + + + + + + + + + + + + - - - Release date - - - - + Version + + + Release date + + + - + File size Release notes - + + There are no release notes for this release. + + diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseViewModel.cs b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseViewModel.cs index d92912c5c..a44cc6e8d 100644 --- a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseViewModel.cs @@ -1,8 +1,13 @@ +using System; using System.Threading; using System.Threading.Tasks; using Artemis.UI.Screens.Workshop.Parameters; using Artemis.UI.Shared.Routing; +using Artemis.UI.Shared.Services; +using Artemis.UI.Shared.Services.Builders; +using Artemis.UI.Shared.Utilities; using Artemis.WebClient.Workshop; +using Artemis.WebClient.Workshop.Handlers.InstallationHandlers; using PropertyChanged.SourceGenerator; using StrawberryShake; @@ -11,20 +16,84 @@ namespace Artemis.UI.Screens.Workshop.EntryReleases; public partial class EntryReleaseViewModel : RoutableScreen { private readonly IWorkshopClient _client; - [Notify] private IGetReleaseById_Release? _release; + private readonly IRouter _router; + private readonly INotificationService _notificationService; + private readonly IWindowService _windowService; + private readonly EntryInstallationHandlerFactory _factory; + private readonly Progress _progress = new(); - public EntryReleaseViewModel(IWorkshopClient client) + [Notify] private IGetReleaseById_Release? _release; + [Notify] private float _installProgress; + [Notify] private bool _installationInProgress; + + private CancellationTokenSource? _cts; + + public EntryReleaseViewModel(IWorkshopClient client, IRouter router, INotificationService notificationService, IWindowService windowService, EntryInstallationHandlerFactory factory) { _client = client; + _router = router; + _notificationService = notificationService; + _windowService = windowService; + _factory = factory; + _progress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage; + } + + public async Task Close() + { + await _router.GoUp(); + } + + public async Task Install() + { + if (Release == null) + return; + + _cts = new CancellationTokenSource(); + InstallProgress = 0; + InstallationInProgress = true; + try + { + IEntryInstallationHandler handler = _factory.CreateHandler(Release.Entry.EntryType); + EntryInstallResult result = await handler.InstallAsync(Release.Entry, Release, _progress, _cts.Token); + if (result.IsSuccess) + _notificationService.CreateNotification().WithTitle("Installation succeeded").WithSeverity(NotificationSeverity.Success).Show(); + else if (!_cts.IsCancellationRequested) + _notificationService.CreateNotification().WithTitle("Installation failed").WithMessage(result.Message).WithSeverity(NotificationSeverity.Error).Show(); + } + catch (Exception e) + { + _windowService.ShowExceptionDialog("Failed to install workshop entry", e); + } + finally + { + InstallationInProgress = false; + } + } + + public void Cancel() + { + _cts?.Cancel(); } /// public override async Task OnNavigating(ReleaseDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken) { IOperationResult result = await _client.GetReleaseById.ExecuteAsync(parameters.ReleaseId, cancellationToken); - if (result.IsErrorResult()) - return; - Release = result.Data?.Release; } + + #region Overrides of RoutableScreen + + /// + public override Task OnClosing(NavigationArguments args) + { + if (!InstallationInProgress) + return Task.CompletedTask; + + args.Cancel(); + _notificationService.CreateNotification().WithMessage("Please wait for the installation to finish").Show(); + return Task.CompletedTask; + } + + #endregion } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml index 899065ec7..cf62f0e5b 100644 --- a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml @@ -2,78 +2,38 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details" xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:workshop="clr-namespace:Artemis.WebClient.Workshop;assembly=Artemis.WebClient.Workshop" + xmlns:entryReleases="clr-namespace:Artemis.UI.Screens.Workshop.EntryReleases" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryReleasesView" - x:DataType="details:EntryReleasesViewModel"> + x:Class="Artemis.UI.Screens.Workshop.EntryReleases.EntryReleasesView" + x:DataType="entryReleases:EntryReleasesViewModel"> + Releases - - - - - - - - - + + - + + + + + + Created + + + + - - - + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml.cs b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml.cs index 523edc7e5..39b603187 100644 --- a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml.cs @@ -1,10 +1,9 @@ -using Avalonia; using Avalonia.Controls; -using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; -namespace Artemis.UI.Screens.Workshop.Entries.Details; +namespace Artemis.UI.Screens.Workshop.EntryReleases; -public partial class EntryReleasesView : UserControl +public partial class EntryReleasesView : ReactiveUserControl { public EntryReleasesView() { diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs index 4ec497e37..c21b841dc 100644 --- a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs @@ -2,51 +2,49 @@ using System.Collections.Generic; using System.Linq; using System.Reactive; -using System.Threading; +using System.Reactive.Disposables; using System.Threading.Tasks; using Artemis.UI.Shared; using Artemis.UI.Shared.Routing; -using Artemis.UI.Shared.Services; -using Artemis.UI.Shared.Services.Builders; -using Artemis.UI.Shared.Utilities; using Artemis.WebClient.Workshop; -using Artemis.WebClient.Workshop.Handlers.InstallationHandlers; using Artemis.WebClient.Workshop.Models; -using Humanizer; +using PropertyChanged.SourceGenerator; using ReactiveUI; -namespace Artemis.UI.Screens.Workshop.Entries.Details; +namespace Artemis.UI.Screens.Workshop.EntryReleases; -public class EntryReleasesViewModel : ViewModelBase +public partial class EntryReleasesViewModel : ActivatableViewModelBase { - private readonly EntryInstallationHandlerFactory _factory; - private readonly IWindowService _windowService; - private readonly INotificationService _notificationService; private readonly IRouter _router; + [Notify] private IRelease? _selectedRelease; - public EntryReleasesViewModel(IEntryDetails entry, EntryInstallationHandlerFactory factory, IWindowService windowService, INotificationService notificationService, IRouter router) + public EntryReleasesViewModel(IEntryDetails entry, IRouter router) { - _factory = factory; - _windowService = windowService; - _notificationService = notificationService; _router = router; Entry = entry; - LatestRelease = Entry.Releases.MaxBy(r => r.CreatedAt); - OtherReleases = Entry.Releases.OrderByDescending(r => r.CreatedAt).Skip(1).Take(4).Cast().ToList(); - - DownloadLatestRelease = ReactiveCommand.CreateFromTask(ExecuteDownloadLatestRelease); - OnInstallationStarted = Confirm; + Releases = Entry.Releases.OrderByDescending(r => r.CreatedAt).Take(5).Cast().ToList(); NavigateToRelease = ReactiveCommand.CreateFromTask(ExecuteNavigateToRelease); + + this.WhenActivated(d => + { + router.CurrentPath.Subscribe(p => SelectedRelease = p != null && p.Contains("releases") && float.TryParse(p.Split('/').Last(), out float releaseId) + ? Releases.FirstOrDefault(r => r.Id == releaseId) + : null) + .DisposeWith(d); + + this.WhenAnyValue(vm => vm.SelectedRelease) + .WhereNotNull() + .Subscribe(s => ExecuteNavigateToRelease(s)) + .DisposeWith(d); + }); } public IEntryDetails Entry { get; } - public IRelease? LatestRelease { get; } - public List OtherReleases { get; } + public List Releases { get; } - public ReactiveCommand DownloadLatestRelease { get; } public ReactiveCommand NavigateToRelease { get; } - + public Func> OnInstallationStarted { get; set; } public Func? OnInstallationFinished { get; set; } @@ -67,39 +65,4 @@ public class EntryReleasesViewModel : ViewModelBase throw new ArgumentOutOfRangeException(nameof(Entry.EntryType)); } } - - private async Task ExecuteDownloadLatestRelease(CancellationToken cancellationToken) - { - if (LatestRelease == null) - return; - - if (await OnInstallationStarted(Entry, LatestRelease)) - return; - - IEntryInstallationHandler installationHandler = _factory.CreateHandler(Entry.EntryType); - EntryInstallResult result = await installationHandler.InstallAsync(Entry, LatestRelease, new Progress(), cancellationToken); - if (result.IsSuccess && result.Entry != null) - { - if (OnInstallationFinished != null) - await OnInstallationFinished(result.Entry); - _notificationService.CreateNotification().WithTitle($"{Entry.EntryType.Humanize(LetterCasing.Sentence)} installed").WithSeverity(NotificationSeverity.Success).Show(); - } - else - { - _notificationService.CreateNotification() - .WithTitle($"Failed to install {Entry.EntryType.Humanize(LetterCasing.LowerCase)}") - .WithMessage(result.Message) - .WithSeverity(NotificationSeverity.Error).Show(); - } - } - - private async Task Confirm(IEntryDetails entryDetails, IRelease release) - { - bool confirm = await _windowService.ShowConfirmContentDialog( - "Install latest release", - $"Are you sure you want to download and install version {release.Version} of {entryDetails.Name}?" - ); - - return !confirm; - } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs index 69853e0f9..c03896bf8 100644 --- a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Screens.Workshop.Entries.Details; +using Artemis.UI.Screens.Workshop.EntryReleases; using Artemis.UI.Screens.Workshop.Layout.Dialogs; using Artemis.UI.Screens.Workshop.Parameters; using Artemis.UI.Shared.Routing; @@ -40,13 +41,15 @@ public partial class LayoutDetailsViewModel : RoutableHostScreen getEntryReleasesViewModel, Func getEntryImagesViewModel) { - LayoutDescriptionViewModel = layoutDescriptionViewModel; _client = client; _deviceService = deviceService; _windowService = windowService; _getEntryInfoViewModel = getEntryInfoViewModel; _getEntryReleasesViewModel = getEntryReleasesViewModel; _getEntryImagesViewModel = getEntryImagesViewModel; + + LayoutDescriptionViewModel = layoutDescriptionViewModel; + RecycleScreen = false; } public LayoutDescriptionViewModel LayoutDescriptionViewModel { get; } diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs index b2c222fbb..a1b6e0939 100644 --- a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs @@ -8,6 +8,7 @@ using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Screens.Workshop.Entries.Details; using Artemis.UI.Screens.Workshop.Entries.List; +using Artemis.UI.Screens.Workshop.EntryReleases; using Artemis.UI.Screens.Workshop.Parameters; using Artemis.UI.Screens.Workshop.Plugins.Dialogs; using Artemis.UI.Shared.Routing; @@ -41,13 +42,15 @@ public partial class PluginDetailsViewModel : RoutableHostScreen getEntryReleasesViewModel, Func getEntryImagesViewModel) { - PluginDescriptionViewModel = pluginDescriptionViewModel; _client = client; _windowService = windowService; _pluginManagementService = pluginManagementService; _getEntryInfoViewModel = getEntryInfoViewModel; _getEntryReleasesViewModel = getEntryReleasesViewModel; _getEntryImagesViewModel = getEntryImagesViewModel; + + PluginDescriptionViewModel = pluginDescriptionViewModel; + RecycleScreen = false; } public PluginDescriptionViewModel PluginDescriptionViewModel { get; } diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs index 1c26abb8d..bd08e49d9 100644 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Artemis.UI.Screens.Workshop.Entries.Details; +using Artemis.UI.Screens.Workshop.EntryReleases; using Artemis.UI.Screens.Workshop.Parameters; using Artemis.UI.Shared.Routing; using Artemis.WebClient.Workshop; @@ -31,12 +32,13 @@ public partial class ProfileDetailsViewModel : RoutableHostScreen getEntryReleasesViewModel, Func getEntryImagesViewModel) { - ProfileDescriptionViewModel = profileDescriptionViewModel; - _client = client; _getEntryInfoViewModel = getEntryInfoViewModel; _getEntryReleasesViewModel = getEntryReleasesViewModel; _getEntryImagesViewModel = getEntryImagesViewModel; + + ProfileDescriptionViewModel = profileDescriptionViewModel; + RecycleScreen = false; } public ProfileDescriptionViewModel ProfileDescriptionViewModel { get; } diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/IEntryInstallationHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/IEntryInstallationHandler.cs index aacd0d580..555ed11c4 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/IEntryInstallationHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/IEntryInstallationHandler.cs @@ -6,6 +6,6 @@ namespace Artemis.WebClient.Workshop.Handlers.InstallationHandlers; public interface IEntryInstallationHandler { - Task InstallAsync(IEntryDetails entry, IRelease release, Progress progress, CancellationToken cancellationToken); + Task InstallAsync(IEntrySummary entry, IRelease release, Progress progress, CancellationToken cancellationToken); Task UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/LayoutEntryInstallationHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/LayoutEntryInstallationHandler.cs index 3014950f7..4e8b469bd 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/LayoutEntryInstallationHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/LayoutEntryInstallationHandler.cs @@ -25,7 +25,7 @@ public class LayoutEntryInstallationHandler : IEntryInstallationHandler _defaultLayoutProvider = defaultLayoutProvider; } - public async Task InstallAsync(IEntryDetails entry, IRelease release, Progress progress, CancellationToken cancellationToken) + public async Task InstallAsync(IEntrySummary entry, IRelease release, Progress progress, CancellationToken cancellationToken) { using MemoryStream stream = new(); diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/PluginEntryInstallationHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/PluginEntryInstallationHandler.cs index 592b13037..476b658dc 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/PluginEntryInstallationHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/PluginEntryInstallationHandler.cs @@ -22,7 +22,7 @@ public class PluginEntryInstallationHandler : IEntryInstallationHandler _pluginManagementService = pluginManagementService; } - public async Task InstallAsync(IEntryDetails entry, IRelease release, Progress progress, CancellationToken cancellationToken) + public async Task InstallAsync(IEntrySummary entry, IRelease release, Progress progress, CancellationToken cancellationToken) { // Ensure there is an installed entry InstalledEntry? installedEntry = _workshopService.GetInstalledEntry(entry.Id); diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs index 1219f84d6..1264c43b7 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs @@ -20,7 +20,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler _workshopService = workshopService; } - public async Task InstallAsync(IEntryDetails entry, IRelease release, Progress progress, CancellationToken cancellationToken) + public async Task InstallAsync(IEntrySummary entry, IRelease release, Progress progress, CancellationToken cancellationToken) { using MemoryStream stream = new(); diff --git a/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs b/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs index ce90444b0..401e5495e 100644 --- a/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs +++ b/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs @@ -16,7 +16,7 @@ public class InstalledEntry Load(); } - public InstalledEntry(IEntryDetails entry, IRelease release) + public InstalledEntry(IEntrySummary entry, IRelease release) { Entity = new EntryEntity(); diff --git a/src/Artemis.WebClient.Workshop/Queries/GetReleaseById.graphql b/src/Artemis.WebClient.Workshop/Queries/GetReleaseById.graphql index 4b9aaa912..84ee9604a 100644 --- a/src/Artemis.WebClient.Workshop/Queries/GetReleaseById.graphql +++ b/src/Artemis.WebClient.Workshop/Queries/GetReleaseById.graphql @@ -2,5 +2,8 @@ query GetReleaseById($id: Long!) { release(id: $id) { ...release changelog + entry { + ...entrySummary + } } } \ No newline at end of file From 6b4ed48d05e2ce14cbdf22946f01c5a824621074 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 30 Mar 2024 17:55:51 +0100 Subject: [PATCH 06/13] Workshop - Reworked installation management --- .../Routable/RoutableScreenOfTParam.cs | 30 ++++- .../Routing/Router/Router.cs | 27 ++++- src/Artemis.UI/Artemis.UI.csproj | 1 + src/Artemis.UI/Routing/Routes.cs | 2 + .../Entries/Details/EntryInfoView.axaml | 5 +- .../Entries/Details/EntryInfoView.axaml.cs | 3 +- .../Entries/Details/EntryInfoViewModel.cs | 33 +++++- .../Entries/List/EntryListItemView.axaml | 4 +- .../EntryReleases/EntryReleaseItemView.axaml | 34 ++++++ .../EntryReleaseItemView.axaml.cs | 11 ++ .../EntryReleaseItemViewModel.cs | 41 +++++++ .../EntryReleases/EntryReleaseView.axaml | 19 ++-- .../EntryReleases/EntryReleaseViewModel.cs | 30 ++++- .../EntryReleases/EntryReleasesView.axaml | 30 +---- .../EntryReleases/EntryReleasesViewModel.cs | 16 +-- .../Workshop/Layout/LayoutDetailsViewModel.cs | 37 ------- .../Workshop/Layout/LayoutManageView.axaml | 46 ++++++++ .../Workshop/Layout/LayoutManageView.axaml.cs | 14 +++ .../Workshop/Layout/LayoutManageViewModel.cs | 103 ++++++++++++++++++ .../Plugins/Dialogs/PluginDialogView.axaml | 19 ---- .../Plugins/Dialogs/PluginDialogView.axaml.cs | 11 -- .../Plugins/Dialogs/PluginDialogViewModel.cs | 23 ---- .../Plugins/PluginDetailsViewModel.cs | 42 ------- .../Workshop/Plugins/PluginManageView.axaml | 33 ++++++ .../Plugins/PluginManageView.axaml.cs | 14 +++ .../Workshop/Plugins/PluginManageViewModel.cs | 77 +++++++++++++ .../Services/Interfaces/IWorkshopService.cs | 2 + .../Services/WorkshopService.cs | 4 + 28 files changed, 514 insertions(+), 197 deletions(-) create mode 100644 src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseItemView.axaml create mode 100644 src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseItemView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseItemViewModel.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Layout/LayoutManageView.axaml create mode 100644 src/Artemis.UI/Screens/Workshop/Layout/LayoutManageView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Layout/LayoutManageViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Workshop/Plugins/Dialogs/PluginDialogView.axaml delete mode 100644 src/Artemis.UI/Screens/Workshop/Plugins/Dialogs/PluginDialogView.axaml.cs delete mode 100644 src/Artemis.UI/Screens/Workshop/Plugins/Dialogs/PluginDialogViewModel.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Plugins/PluginManageView.axaml create mode 100644 src/Artemis.UI/Screens/Workshop/Plugins/PluginManageView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Plugins/PluginManageViewModel.cs diff --git a/src/Artemis.UI.Shared/Routing/Routable/RoutableScreenOfTParam.cs b/src/Artemis.UI.Shared/Routing/Routable/RoutableScreenOfTParam.cs index bbe71fe2a..52e8a5e48 100644 --- a/src/Artemis.UI.Shared/Routing/Routable/RoutableScreenOfTParam.cs +++ b/src/Artemis.UI.Shared/Routing/Routable/RoutableScreenOfTParam.cs @@ -13,6 +13,11 @@ namespace Artemis.UI.Shared.Routing; /// The type of parameters the screen expects. It must have a parameterless constructor. public abstract class RoutableScreen : RoutableScreen, IRoutableScreen where TParam : new() { + /// + /// Gets or sets the parameter source of the screen. + /// + protected ParameterSource ParameterSource { get; set; } = ParameterSource.Segment; + /// /// Called while navigating to this screen. /// @@ -26,15 +31,16 @@ public abstract class RoutableScreen : RoutableScreen, IRoutableScreen w { return Task.CompletedTask; } - + async Task IRoutableScreen.InternalOnNavigating(NavigationArguments args, CancellationToken cancellationToken) { Func activator = GetParameterActivator(); - if (args.SegmentParameters.Length != _parameterPropertyCount) - throw new ArtemisRoutingException($"Did not retrieve the required amount of parameters, expects {_parameterPropertyCount}, got {args.SegmentParameters.Length}."); + object[] routeParameters = ParameterSource == ParameterSource.Segment ? args.SegmentParameters : args.RouteParameters; + if (routeParameters.Length != _parameterPropertyCount) + throw new ArtemisRoutingException($"Did not retrieve the required amount of parameters, expects {_parameterPropertyCount}, got {routeParameters.Length}."); - TParam parameters = activator(args.SegmentParameters); + TParam parameters = activator(routeParameters); await OnNavigating(args, cancellationToken); await OnNavigating(parameters, args, cancellationToken); } @@ -97,4 +103,20 @@ public abstract class RoutableScreen : RoutableScreen, IRoutableScreen w } #endregion +} + +/// +/// Enum representing the source of parameters in the RoutableScreen class. +/// +public enum ParameterSource +{ + /// + /// Represents the source where parameters are obtained from the segment of the route. + /// + Segment, + + /// + /// Represents the source where parameters are obtained from the entire route. + /// + Route } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Routing/Router/Router.cs b/src/Artemis.UI.Shared/Routing/Router/Router.cs index 3f1263a29..269d51fce 100644 --- a/src/Artemis.UI.Shared/Routing/Router/Router.cs +++ b/src/Artemis.UI.Shared/Routing/Router/Router.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reactive.Subjects; using System.Threading.Tasks; using Artemis.Core; @@ -72,7 +73,13 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable /// public async Task Navigate(string path, RouterNavigationOptions? options = null) { - path = path.ToLower().Trim(' ', '/', '\\'); + if (path.StartsWith('/') && _currentRouteSubject.Value != null) + path = _currentRouteSubject.Value + path; + if (path.StartsWith("../") && _currentRouteSubject.Value != null) + path = NavigateUp(_currentRouteSubject.Value, path); + else + path = path.ToLower().Trim(' ', '/', '\\'); + options ??= new RouterNavigationOptions(); // Routing takes place on the UI thread with processing heavy tasks offloaded by the router itself @@ -216,6 +223,24 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable _logger.Debug("Router disposed, should that be? Stacktrace: \r\n{StackTrace}", Environment.StackTrace); } + + + private string NavigateUp(string current, string path) + { + string[] pathParts = current.Split('/'); + string[] navigateParts = path.Split('/'); + int upCount = navigateParts.TakeWhile(part => part == "..").Count(); + + if (upCount >= pathParts.Length) + { + throw new InvalidOperationException("Cannot navigate up beyond the root"); + } + + IEnumerable remainingCurrentPathParts = pathParts.Take(pathParts.Length - upCount); + IEnumerable remainingNavigatePathParts = navigateParts.Skip(upCount); + + return string.Join("/", remainingCurrentPathParts.Concat(remainingNavigatePathParts)); + } private void MainWindowServiceOnMainWindowOpened(object? sender, EventArgs e) { diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index c52465960..1714435f1 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -65,5 +65,6 @@ + \ No newline at end of file diff --git a/src/Artemis.UI/Routing/Routes.cs b/src/Artemis.UI/Routing/Routes.cs index e64c17868..cac359e3d 100644 --- a/src/Artemis.UI/Routing/Routes.cs +++ b/src/Artemis.UI/Routing/Routes.cs @@ -30,6 +30,7 @@ namespace Artemis.UI.Routing new RouteRegistration("entries", [ new RouteRegistration("plugins", [ new RouteRegistration("details/{entryId:long}", [ + new RouteRegistration("manage"), new RouteRegistration("releases/{releaseId:long}") ]) ]), @@ -40,6 +41,7 @@ namespace Artemis.UI.Routing ]), new RouteRegistration("layouts", [ new RouteRegistration("details/{entryId:long}", [ + new RouteRegistration("manage"), new RouteRegistration("releases/{releaseId:long}") ]) ]) diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoView.axaml index 2d0ecbffa..52f1b8dd3 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoView.axaml @@ -32,7 +32,6 @@ - Updated + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoView.axaml.cs index 5c6ec0a0c..2d9a96dc7 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoView.axaml.cs @@ -1,10 +1,11 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; namespace Artemis.UI.Screens.Workshop.Entries.Details; -public partial class EntryInfoView : UserControl +public partial class EntryInfoView : ReactiveUserControl { public EntryInfoView() { diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoViewModel.cs index cd37606ea..8fc0f1b33 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntryInfoViewModel.cs @@ -1,29 +1,54 @@ using System; using System.Linq; +using System.Reactive.Disposables; +using System.Reactive.Linq; using System.Threading.Tasks; using Artemis.Core; using Artemis.UI.Shared; +using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Services; using Artemis.WebClient.Workshop; +using Artemis.WebClient.Workshop.Models; +using Artemis.WebClient.Workshop.Services; +using PropertyChanged.SourceGenerator; +using ReactiveUI; namespace Artemis.UI.Screens.Workshop.Entries.Details; -public class EntryInfoViewModel : ViewModelBase +public partial class EntryInfoViewModel : ActivatableViewModelBase { + private readonly IRouter _router; private readonly INotificationService _notificationService; - public IEntryDetails Entry { get; } - public DateTimeOffset? UpdatedAt { get; } + [Notify] private bool _canBeManaged; - public EntryInfoViewModel(IEntryDetails entry, INotificationService notificationService) + public EntryInfoViewModel(IEntryDetails entry, IRouter router, INotificationService notificationService, IWorkshopService workshopService) { + _router = router; _notificationService = notificationService; Entry = entry; UpdatedAt = Entry.Releases.Any() ? Entry.Releases.Max(r => r.CreatedAt) : Entry.CreatedAt; + CanBeManaged = Entry.EntryType != EntryType.Profile && workshopService.GetInstalledEntry(entry.Id) != null; + + this.WhenActivated(d => + { + Observable.FromEventPattern(x => workshopService.OnInstalledEntrySaved += x, x => workshopService.OnInstalledEntrySaved -= x) + .StartWith([]) + .Subscribe(_ => CanBeManaged = Entry.EntryType != EntryType.Profile && workshopService.GetInstalledEntry(entry.Id) != null) + .DisposeWith(d); + }); } + public IEntryDetails Entry { get; } + public DateTimeOffset? UpdatedAt { get; } + public async Task CopyShareLink() { await Shared.UI.Clipboard.SetTextAsync($"{WorkshopConstants.WORKSHOP_URL}/entries/{Entry.Id}/{StringUtilities.UrlFriendly(Entry.Name)}"); _notificationService.CreateNotification().WithTitle("Copied share link to clipboard.").Show(); } + + public async Task GoToManage() + { + await _router.Navigate("/manage"); + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemView.axaml index 91932033a..712d7fc68 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Entries/List/EntryListItemView.axaml @@ -79,11 +79,11 @@ - + installed - + update available diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseItemView.axaml b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseItemView.axaml new file mode 100644 index 000000000..1a328d64a --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseItemView.axaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + Created + + + + + + diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseItemView.axaml.cs b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseItemView.axaml.cs new file mode 100644 index 000000000..ffc325a79 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseItemView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.EntryReleases; + +public partial class EntryReleaseItemView : ReactiveUserControl +{ + public EntryReleaseItemView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseItemViewModel.cs b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseItemViewModel.cs new file mode 100644 index 000000000..bafa65507 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseItemViewModel.cs @@ -0,0 +1,41 @@ +using System; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using Artemis.UI.Shared; +using Artemis.WebClient.Workshop; +using Artemis.WebClient.Workshop.Models; +using Artemis.WebClient.Workshop.Services; +using PropertyChanged.SourceGenerator; +using ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.EntryReleases; + +public partial class EntryReleaseItemViewModel : ActivatableViewModelBase +{ + private readonly IWorkshopService _workshopService; + private readonly IEntryDetails _entry; + [Notify] private bool _isCurrentVersion; + + public EntryReleaseItemViewModel(IWorkshopService workshopService, IEntryDetails entry, IRelease release) + { + _workshopService = workshopService; + _entry = entry; + + Release = release; + UpdateIsCurrentVersion(); + + this.WhenActivated(d => + { + Observable.FromEventPattern(x => _workshopService.OnInstalledEntrySaved += x, x => _workshopService.OnInstalledEntrySaved -= x) + .Subscribe(_ => UpdateIsCurrentVersion()) + .DisposeWith(d); + }); + } + + public IRelease Release { get; } + + private void UpdateIsCurrentVersion() + { + IsCurrentVersion = _workshopService.GetInstalledEntry(_entry.Id)?.ReleaseId == Release.Id; + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml index 6bdededd8..3185f1a05 100644 --- a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml @@ -46,7 +46,7 @@ Release info - + - - + + + + + diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseViewModel.cs b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseViewModel.cs index a44cc6e8d..32b1de42a 100644 --- a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseViewModel.cs @@ -8,6 +8,7 @@ using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Utilities; using Artemis.WebClient.Workshop; using Artemis.WebClient.Workshop.Handlers.InstallationHandlers; +using Artemis.WebClient.Workshop.Services; using PropertyChanged.SourceGenerator; using StrawberryShake; @@ -19,21 +20,25 @@ public partial class EntryReleaseViewModel : RoutableScreen _progress = new(); [Notify] private IGetReleaseById_Release? _release; [Notify] private float _installProgress; [Notify] private bool _installationInProgress; + [Notify] private bool _isCurrentVersion; private CancellationTokenSource? _cts; - public EntryReleaseViewModel(IWorkshopClient client, IRouter router, INotificationService notificationService, IWindowService windowService, EntryInstallationHandlerFactory factory) + public EntryReleaseViewModel(IWorkshopClient client, IRouter router, INotificationService notificationService, IWindowService windowService, IWorkshopService workshopService, + EntryInstallationHandlerFactory factory) { _client = client; _router = router; _notificationService = notificationService; _windowService = windowService; + _workshopService = workshopService; _factory = factory; _progress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage; } @@ -56,18 +61,32 @@ public partial class EntryReleaseViewModel : RoutableScreen result = await _client.GetReleaseById.ExecuteAsync(parameters.ReleaseId, cancellationToken); Release = result.Data?.Release; + IsCurrentVersion = Release != null && _workshopService.GetInstalledEntry(Release.Entry.Id)?.ReleaseId == Release.Id; } #region Overrides of RoutableScreen diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml index cf62f0e5b..0e0346d83 100644 --- a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesView.axaml @@ -2,38 +2,12 @@ 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:converters="clr-namespace:Artemis.UI.Converters" - xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" - xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" - xmlns:workshop="clr-namespace:Artemis.WebClient.Workshop;assembly=Artemis.WebClient.Workshop" xmlns:entryReleases="clr-namespace:Artemis.UI.Screens.Workshop.EntryReleases" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Workshop.EntryReleases.EntryReleasesView" - x:DataType="entryReleases:EntryReleasesViewModel"> - - - - - - + x:DataType="entryReleases:EntryReleasesViewModel"> Releases - - - - - - - - - - Created - - - - - - - + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs index c21b841dc..6b2b344f8 100644 --- a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs @@ -16,38 +16,34 @@ namespace Artemis.UI.Screens.Workshop.EntryReleases; public partial class EntryReleasesViewModel : ActivatableViewModelBase { private readonly IRouter _router; - [Notify] private IRelease? _selectedRelease; + [Notify] private EntryReleaseItemViewModel? _selectedRelease; - public EntryReleasesViewModel(IEntryDetails entry, IRouter router) + public EntryReleasesViewModel(IEntryDetails entry, IRouter router, Func getEntryReleaseItemViewModel) { _router = router; Entry = entry; - Releases = Entry.Releases.OrderByDescending(r => r.CreatedAt).Take(5).Cast().ToList(); + Releases = Entry.Releases.OrderByDescending(r => r.CreatedAt).Take(5).Select(r => getEntryReleaseItemViewModel(r)).ToList(); NavigateToRelease = ReactiveCommand.CreateFromTask(ExecuteNavigateToRelease); this.WhenActivated(d => { router.CurrentPath.Subscribe(p => SelectedRelease = p != null && p.Contains("releases") && float.TryParse(p.Split('/').Last(), out float releaseId) - ? Releases.FirstOrDefault(r => r.Id == releaseId) + ? Releases.FirstOrDefault(r => r.Release.Id == releaseId) : null) .DisposeWith(d); this.WhenAnyValue(vm => vm.SelectedRelease) .WhereNotNull() - .Subscribe(s => ExecuteNavigateToRelease(s)) + .Subscribe(s => ExecuteNavigateToRelease(s.Release)) .DisposeWith(d); }); } public IEntryDetails Entry { get; } - public List Releases { get; } - + public List Releases { get; } public ReactiveCommand NavigateToRelease { get; } - public Func> OnInstallationStarted { get; set; } - public Func? OnInstallationFinished { get; set; } - private async Task ExecuteNavigateToRelease(IRelease release) { switch (Entry.EntryType) diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs index c03896bf8..f4d797310 100644 --- a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs @@ -1,20 +1,11 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Artemis.Core; -using Artemis.Core.Services; using Artemis.UI.Screens.Workshop.Entries.Details; using Artemis.UI.Screens.Workshop.EntryReleases; -using Artemis.UI.Screens.Workshop.Layout.Dialogs; using Artemis.UI.Screens.Workshop.Parameters; using Artemis.UI.Shared.Routing; -using Artemis.UI.Shared.Services; using Artemis.WebClient.Workshop; -using Artemis.WebClient.Workshop.Models; -using Artemis.WebClient.Workshop.Services; using PropertyChanged.SourceGenerator; using StrawberryShake; @@ -23,8 +14,6 @@ namespace Artemis.UI.Screens.Workshop.Layout; public partial class LayoutDetailsViewModel : RoutableHostScreen { private readonly IWorkshopClient _client; - private readonly IDeviceService _deviceService; - private readonly IWindowService _windowService; private readonly Func _getEntryInfoViewModel; private readonly Func _getEntryReleasesViewModel; private readonly Func _getEntryImagesViewModel; @@ -34,16 +23,12 @@ public partial class LayoutDetailsViewModel : RoutableHostScreen getEntryInfoViewModel, Func getEntryReleasesViewModel, Func getEntryImagesViewModel) { _client = client; - _deviceService = deviceService; - _windowService = windowService; _getEntryInfoViewModel = getEntryInfoViewModel; _getEntryReleasesViewModel = getEntryReleasesViewModel; _getEntryImagesViewModel = getEntryImagesViewModel; @@ -70,28 +55,6 @@ public partial class LayoutDetailsViewModel : RoutableHostScreen devices = _deviceService.Devices.Where(d => d.RgbDevice.DeviceInfo.DeviceType == layout.RgbLayout.Type).ToList(); - - // If any are found, offer to apply - if (devices.Any()) - { - await _windowService.CreateContentDialog() - .WithTitle("Apply layout to devices") - .WithViewModel(out DeviceSelectionDialogViewModel vm, devices, installedEntry) - .WithCloseButtonText(null) - .HavingPrimaryButton(b => b.WithText("Continue").WithCommand(vm.Apply)) - .ShowAsync(); - } - } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutManageView.axaml b/src/Artemis.UI/Screens/Workshop/Layout/LayoutManageView.axaml new file mode 100644 index 000000000..af6b25fc2 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutManageView.axaml @@ -0,0 +1,46 @@ + + + + + + Manage layout + + + + + + This layout is made for devices of type + . + Unfortunately, none were detected. + + + + Select the devices on which you would like to apply the downloaded layout. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutManageView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Layout/LayoutManageView.axaml.cs new file mode 100644 index 000000000..bc26260c9 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutManageView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Layout; + +public partial class LayoutManageView : ReactiveUserControl +{ + public LayoutManageView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutManageViewModel.cs b/src/Artemis.UI/Screens/Workshop/Layout/LayoutManageViewModel.cs new file mode 100644 index 000000000..9ce907d57 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutManageViewModel.cs @@ -0,0 +1,103 @@ +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Reactive; +using System.Threading; +using System.Threading.Tasks; +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.UI.DryIoc.Factories; +using Artemis.UI.Screens.SurfaceEditor; +using Artemis.UI.Screens.Workshop.Parameters; +using Artemis.UI.Shared.Routing; +using Artemis.UI.Shared.Services; +using Artemis.WebClient.Workshop.Models; +using Artemis.WebClient.Workshop.Providers; +using Artemis.WebClient.Workshop.Services; +using Avalonia.Threading; +using PropertyChanged.SourceGenerator; +using ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Layout; + +public partial class LayoutManageViewModel : RoutableScreen +{ + private readonly ISurfaceVmFactory _surfaceVmFactory; + private readonly IRouter _router; + private readonly IWorkshopService _workshopService; + private readonly IDeviceService _deviceService; + private readonly WorkshopLayoutProvider _layoutProvider; + private readonly IWindowService _windowService; + [Notify] private ArtemisLayout? _layout; + [Notify] private InstalledEntry? _entry; + [Notify] private ObservableCollection? _devices; + + public LayoutManageViewModel(ISurfaceVmFactory surfaceVmFactory, + IRouter router, + IWorkshopService workshopService, + IDeviceService deviceService, + WorkshopLayoutProvider layoutProvider, + IWindowService windowService) + { + _surfaceVmFactory = surfaceVmFactory; + _router = router; + _workshopService = workshopService; + _deviceService = deviceService; + _layoutProvider = layoutProvider; + _windowService = windowService; + Apply = ReactiveCommand.Create(ExecuteApply); + ParameterSource = ParameterSource.Route; + } + + public ReactiveCommand Apply { get; } + + public async Task Close() + { + await _router.GoUp(); + } + + public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken) + { + InstalledEntry? installedEntry = _workshopService.GetInstalledEntry(parameters.EntryId); + if (installedEntry == null) + { + // TODO: Fix cancelling without this workaround, currently navigation is stopped but the page still opens + Dispatcher.UIThread.InvokeAsync(async () => + { + await _windowService.ShowConfirmContentDialog("Entry not found", "The entry you're trying to manage could not be found.", "Go back", null); + await Close(); + }); + return; + } + + Layout = new ArtemisLayout(Path.Combine(installedEntry.GetReleaseDirectory().FullName, "layout.xml")); + if (!Layout.IsValid) + { + // TODO: Fix cancelling without this workaround, currently navigation is stopped but the page still opens + Dispatcher.UIThread.InvokeAsync(async () => + { + await _windowService.ShowConfirmContentDialog("Invalid layout", "The layout of the entry you're trying to manage is invalid.", "Go back", null); + await Close(); + }); + return; + } + + Entry = installedEntry; + Devices = new ObservableCollection(_deviceService.Devices + .Where(d => d.RgbDevice.DeviceInfo.DeviceType == Layout.RgbLayout.Type) + .Select(_surfaceVmFactory.ListDeviceViewModel)); + } + + private void ExecuteApply() + { + if (Devices == null) + return; + + foreach (ListDeviceViewModel listDeviceViewModel in Devices.Where(d => d.IsSelected)) + { + _layoutProvider.ConfigureDevice(listDeviceViewModel.Device, Entry); + _deviceService.SaveDevice(listDeviceViewModel.Device); + _deviceService.LoadDeviceLayout(listDeviceViewModel.Device); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/Dialogs/PluginDialogView.axaml b/src/Artemis.UI/Screens/Workshop/Plugins/Dialogs/PluginDialogView.axaml deleted file mode 100644 index 3c0336a37..000000000 --- a/src/Artemis.UI/Screens/Workshop/Plugins/Dialogs/PluginDialogView.axaml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - Plugin features - - - - - diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/Dialogs/PluginDialogView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Plugins/Dialogs/PluginDialogView.axaml.cs deleted file mode 100644 index 19329553e..000000000 --- a/src/Artemis.UI/Screens/Workshop/Plugins/Dialogs/PluginDialogView.axaml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Avalonia.ReactiveUI; - -namespace Artemis.UI.Screens.Workshop.Plugins.Dialogs; - -public partial class PluginDialogView : ReactiveUserControl -{ - public PluginDialogView() - { - InitializeComponent(); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/Dialogs/PluginDialogViewModel.cs b/src/Artemis.UI/Screens/Workshop/Plugins/Dialogs/PluginDialogViewModel.cs deleted file mode 100644 index 863417326..000000000 --- a/src/Artemis.UI/Screens/Workshop/Plugins/Dialogs/PluginDialogViewModel.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.ObjectModel; -using System.Linq; -using System.Reactive.Linq; -using Artemis.Core; -using Artemis.UI.DryIoc.Factories; -using Artemis.UI.Screens.Plugins; -using Artemis.UI.Screens.Plugins.Features; -using Artemis.UI.Shared; -using ReactiveUI; - -namespace Artemis.UI.Screens.Workshop.Plugins.Dialogs; - -public class PluginDialogViewModel : ContentDialogViewModelBase -{ - public PluginDialogViewModel(Plugin plugin, ISettingsVmFactory settingsVmFactory) - { - PluginViewModel = settingsVmFactory.PluginViewModel(plugin, ReactiveCommand.Create(() => {}, Observable.Empty())); - PluginFeatures = new ObservableCollection(plugin.Features.Select(f => settingsVmFactory.PluginFeatureViewModel(f, false))); - } - - public PluginViewModel PluginViewModel { get; } - public ObservableCollection PluginFeatures { get; } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs index a1b6e0939..cfab9166c 100644 --- a/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginDetailsViewModel.cs @@ -1,20 +1,13 @@ using System; -using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Artemis.Core; -using Artemis.Core.Services; using Artemis.UI.Screens.Workshop.Entries.Details; using Artemis.UI.Screens.Workshop.Entries.List; using Artemis.UI.Screens.Workshop.EntryReleases; using Artemis.UI.Screens.Workshop.Parameters; -using Artemis.UI.Screens.Workshop.Plugins.Dialogs; using Artemis.UI.Shared.Routing; -using Artemis.UI.Shared.Services; using Artemis.WebClient.Workshop; -using Artemis.WebClient.Workshop.Models; using PropertyChanged.SourceGenerator; using StrawberryShake; @@ -23,8 +16,6 @@ namespace Artemis.UI.Screens.Workshop.Plugins; public partial class PluginDetailsViewModel : RoutableHostScreen { private readonly IWorkshopClient _client; - private readonly IWindowService _windowService; - private readonly IPluginManagementService _pluginManagementService; private readonly Func _getEntryInfoViewModel; private readonly Func _getEntryReleasesViewModel; private readonly Func _getEntryImagesViewModel; @@ -35,16 +26,12 @@ public partial class PluginDetailsViewModel : RoutableHostScreen? _dependants; public PluginDetailsViewModel(IWorkshopClient client, - IWindowService windowService, - IPluginManagementService pluginManagementService, PluginDescriptionViewModel pluginDescriptionViewModel, Func getEntryInfoViewModel, Func getEntryReleasesViewModel, Func getEntryImagesViewModel) { _client = client; - _windowService = windowService; - _pluginManagementService = pluginManagementService; _getEntryInfoViewModel = getEntryInfoViewModel; _getEntryReleasesViewModel = getEntryReleasesViewModel; _getEntryImagesViewModel = getEntryImagesViewModel; @@ -72,35 +59,6 @@ public partial class PluginDetailsViewModel : RoutableHostScreen OnInstallationStarted(IEntryDetails entryDetails, IRelease release) - { - bool confirm = await _windowService.ShowConfirmContentDialog( - "Installing plugin", - $"You are about to install version {release.Version} of {entryDetails.Name}. \r\n\r\n" + - "Plugins are NOT verified by Artemis and could harm your PC, if you have doubts about a plugin please ask on Discord!", - "I trust this plugin, install it" - ); - - return !confirm; - } - - private async Task OnInstallationFinished(InstalledEntry installedEntry) - { - if (!installedEntry.TryGetMetadata("PluginId", out Guid pluginId)) - return; - Plugin? plugin = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginId); - if (plugin == null) - return; - - await _windowService.CreateContentDialog().WithTitle("Manage plugin").WithViewModel(out PluginDialogViewModel _, plugin).WithFullScreen().ShowAsync(); - } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginManageView.axaml b/src/Artemis.UI/Screens/Workshop/Plugins/PluginManageView.axaml new file mode 100644 index 000000000..c6fe8c059 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginManageView.axaml @@ -0,0 +1,33 @@ + + + + + + Manage plugin + + + + + + + + + + Plugin features + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginManageView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Plugins/PluginManageView.axaml.cs new file mode 100644 index 000000000..d5136c54e --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginManageView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Plugins; + +public partial class PluginManageView : ReactiveUserControl +{ + public PluginManageView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginManageViewModel.cs b/src/Artemis.UI/Screens/Workshop/Plugins/PluginManageViewModel.cs new file mode 100644 index 000000000..9bd558217 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginManageViewModel.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +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.Screens.Plugins.Features; +using Artemis.UI.Screens.Workshop.Parameters; +using Artemis.UI.Shared.Routing; +using Artemis.UI.Shared.Services; +using Artemis.WebClient.Workshop.Models; +using Artemis.WebClient.Workshop.Services; +using Avalonia.Threading; +using PropertyChanged.SourceGenerator; +using ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Plugins; + +public partial class PluginManageViewModel : RoutableScreen +{ + private readonly ISettingsVmFactory _settingsVmFactory; + private readonly IRouter _router; + private readonly IWorkshopService _workshopService; + private readonly IPluginManagementService _pluginManagementService; + private readonly IWindowService _windowService; + [Notify] private PluginViewModel? _pluginViewModel; + [Notify] private ObservableCollection? _pluginFeatures; + + public PluginManageViewModel(ISettingsVmFactory settingsVmFactory, IRouter router, IWorkshopService workshopService, IPluginManagementService pluginManagementService, IWindowService windowService) + { + _settingsVmFactory = settingsVmFactory; + _router = router; + _workshopService = workshopService; + _pluginManagementService = pluginManagementService; + _windowService = windowService; + ParameterSource = ParameterSource.Route; + } + + public async Task Close() + { + await _router.GoUp(); + } + + /// + public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken) + { + InstalledEntry? installedEntry = _workshopService.GetInstalledEntry(parameters.EntryId); + if (installedEntry == null || !installedEntry.TryGetMetadata("PluginId", out Guid pluginId)) + { + // TODO: Fix cancelling without this workaround, currently navigation is stopped but the page still opens + Dispatcher.UIThread.InvokeAsync(async () => + { + await _windowService.ShowConfirmContentDialog("Invalid plugin", "The plugin you're trying to manage is invalid or doesn't exist", "Go back", null); + await Close(); + }); + return; + } + + Plugin? plugin = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginId); + if (plugin == null) + { + // TODO: Fix cancelling without this workaround, currently navigation is stopped but the page still opens + Dispatcher.UIThread.InvokeAsync(async () => + { + await _windowService.ShowConfirmContentDialog("Invalid plugin", "The plugin you're trying to manage is invalid or doesn't exist", "Go back", null); + await Close(); + }); + return; + } + + PluginViewModel = _settingsVmFactory.PluginViewModel(plugin, ReactiveCommand.Create(() => { })); + PluginFeatures = new ObservableCollection(plugin.Features.Select(f => _settingsVmFactory.PluginFeatureViewModel(f, false))); + } +} \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs b/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs index ea61b3b6d..304f4ef05 100644 --- a/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs +++ b/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs @@ -20,4 +20,6 @@ public interface IWorkshopService void Initialize(); public record WorkshopStatus(bool IsReachable, string Message); + + event EventHandler? OnInstalledEntrySaved; } \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs b/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs index 95b90ccc5..715a5a1bb 100644 --- a/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs +++ b/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs @@ -172,6 +172,8 @@ public class WorkshopService : IWorkshopService { entry.Save(); _entryRepository.Save(entry.Entity); + + OnInstalledEntrySaved?.Invoke(this, entry); } /// @@ -231,4 +233,6 @@ public class WorkshopService : IWorkshopService _logger.Warning(e, "Failed to remove orphaned workshop entry at {Directory}", directory); } } + + public event EventHandler? OnInstalledEntrySaved; } \ No newline at end of file From f152812064899da9217759181bb72e6f3bb5203b Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 30 Mar 2024 20:28:01 +0100 Subject: [PATCH 07/13] Device properties - Show layout error state --- src/Artemis.Core/Models/Surface/LayoutSelection.cs | 10 ++++++++++ src/Artemis.Core/Services/DeviceService.cs | 5 +++-- .../Device/Tabs/Layout/DeviceLayoutTabView.axaml | 4 ++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Artemis.Core/Models/Surface/LayoutSelection.cs b/src/Artemis.Core/Models/Surface/LayoutSelection.cs index f64c54eb1..f88f2ac0c 100644 --- a/src/Artemis.Core/Models/Surface/LayoutSelection.cs +++ b/src/Artemis.Core/Models/Surface/LayoutSelection.cs @@ -7,6 +7,7 @@ public class LayoutSelection : CorePropertyChanged { private string? _type; private string? _parameter; + private string? _errorState; /// /// Gets or sets what kind of layout reference this is. @@ -25,4 +26,13 @@ public class LayoutSelection : CorePropertyChanged get => _parameter; set => SetAndNotify(ref _parameter, value); } + + /// + /// Gets or sets the error state of the layout reference. + /// + public string? ErrorState + { + get => _errorState; + set => SetAndNotify(ref _errorState, value); + } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/DeviceService.cs b/src/Artemis.Core/Services/DeviceService.cs index da81a82a6..111594c4a 100644 --- a/src/Artemis.Core/Services/DeviceService.cs +++ b/src/Artemis.Core/Services/DeviceService.cs @@ -184,13 +184,14 @@ internal class DeviceService : IDeviceService device.ApplyLayout(null, false, false); else provider?.ApplyLayout(device, layout); + + UpdateLeds(); } catch (Exception e) { + device.LayoutSelection.ErrorState = e.Message; _logger.Error(e, "Failed to apply device layout"); } - - UpdateLeds(); } /// diff --git a/src/Artemis.UI/Screens/Device/Tabs/Layout/DeviceLayoutTabView.axaml b/src/Artemis.UI/Screens/Device/Tabs/Layout/DeviceLayoutTabView.axaml index 9dccbfef4..e741e286c 100644 --- a/src/Artemis.UI/Screens/Device/Tabs/Layout/DeviceLayoutTabView.axaml +++ b/src/Artemis.UI/Screens/Device/Tabs/Layout/DeviceLayoutTabView.axaml @@ -83,6 +83,10 @@ + From ff2e57aeaa21960192d7a8d9a5fa28fef244e784 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 30 Mar 2024 20:40:24 +0100 Subject: [PATCH 08/13] Device properties - We can do a bit better with UX than that :P --- .../Tabs/Layout/DeviceLayoutTabView.axaml | 197 +++++++++--------- 1 file changed, 102 insertions(+), 95 deletions(-) diff --git a/src/Artemis.UI/Screens/Device/Tabs/Layout/DeviceLayoutTabView.axaml b/src/Artemis.UI/Screens/Device/Tabs/Layout/DeviceLayoutTabView.axaml index e741e286c..5b4ffc40f 100644 --- a/src/Artemis.UI/Screens/Device/Tabs/Layout/DeviceLayoutTabView.axaml +++ b/src/Artemis.UI/Screens/Device/Tabs/Layout/DeviceLayoutTabView.axaml @@ -9,104 +9,111 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800" x:Class="Artemis.UI.Screens.Device.Layout.DeviceLayoutTabView" x:DataType="layout:DeviceLayoutTabViewModel"> - - - - - - - - - - - - - + + - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - + + + + + + + - - + + + + + + + + + + - - - - - - View workshop page - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailView.axaml.cs deleted file mode 100644 index 729f02a2b..000000000 --- a/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailView.axaml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Avalonia.ReactiveUI; - -namespace Artemis.UI.Screens.Workshop.Library; - -public partial class SubmissionDetailView : ReactiveUserControl -{ - public SubmissionDetailView() - { - InitializeComponent(); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsView.axaml b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsView.axaml new file mode 100644 index 000000000..11cb73c81 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsView.axaml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsView.axaml.cs new file mode 100644 index 000000000..77a1f67ec --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Library; + +public partial class SubmissionDetailsView : ReactiveUserControl +{ + public SubmissionDetailsView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsViewModel.cs similarity index 83% rename from src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailViewModel.cs rename to src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsViewModel.cs index 839511a5d..1011f8045 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsViewModel.cs @@ -8,8 +8,7 @@ using System.Reactive; using System.Threading; using System.Threading.Tasks; using Artemis.UI.Screens.Workshop.Image; -using Artemis.UI.Screens.Workshop.Parameters; -using Artemis.UI.Screens.Workshop.SubmissionWizard; +using Artemis.UI.Shared; using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Services; using Artemis.WebClient.Workshop; @@ -25,7 +24,7 @@ using EntrySpecificationsViewModel = Artemis.UI.Screens.Workshop.Entries.Details namespace Artemis.UI.Screens.Workshop.Library; -public partial class SubmissionDetailViewModel : RoutableScreen +public partial class SubmissionDetailsViewModel : RoutableScreen { private readonly IWorkshopClient _client; private readonly IWindowService _windowService; @@ -40,7 +39,7 @@ public partial class SubmissionDetailViewModel : RoutableScreen vm.HasChanges)); SaveChanges = ReactiveCommand.CreateFromTask(ExecuteSaveChanges, this.WhenAnyValue(vm => vm.HasChanges)); } - + public ObservableCollection Images { get; } = new(); - public ReactiveCommand CreateRelease { get; } - public ReactiveCommand DeleteSubmission { get; } - public ReactiveCommand ViewWorkshopPage { get; } public ReactiveCommand AddImage { get; } public ReactiveCommand SaveChanges { get; } public ReactiveCommand DiscardChanges { get; } - public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken) + public async Task SetEntry(IGetSubmittedEntryById_Entry? entry, CancellationToken cancellationToken) { - IOperationResult result = await _client.GetSubmittedEntryById.ExecuteAsync(parameters.EntryId, cancellationToken); - if (result.IsErrorResult()) - return; - - Entry = result.Data?.Entry; + Entry = entry; await ApplyDetailsFromEntry(cancellationToken); - ApplyImagesFromEntry(); } - public override async Task OnClosing(NavigationArguments args) + public async Task OnClosing(NavigationArguments args) { if (!HasChanges) return; @@ -243,30 +231,7 @@ public partial class SubmissionDetailViewModel : RoutableScreen(Entry); - } - - private async Task ExecuteDeleteSubmission(CancellationToken cancellationToken) - { - if (Entry == null) - return; - - bool confirmed = await _windowService.ShowConfirmContentDialog( - "Delete submission?", - "You cannot undo this by yourself.\r\n" + - "Users that have already downloaded your submission will keep it."); - if (!confirmed) - return; - - IOperationResult result = await _client.RemoveEntry.ExecuteAsync(Entry.Id, cancellationToken); - result.EnsureNoErrors(); - await _router.Navigate("workshop/library/submissions"); - } - + private async Task ExecuteAddImage(CancellationToken arg) { string[]? result = await _windowService.CreateOpenFileDialog().WithAllowMultiple().HavingFilter(f => f.WithBitmaps()).ShowAsync(); @@ -297,12 +262,6 @@ public partial class SubmissionDetailViewModel : RoutableScreen + + + + + + + + Management + + + + + + downloads + + + + + Created + + + + + + + + + + + + + + Releases + + + + + + + + + + Created + + + + + + + + + + View workshop page + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionManagementView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionManagementView.axaml.cs new file mode 100644 index 000000000..2c5493d7a --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionManagementView.axaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Reactive.Disposables; +using Avalonia.ReactiveUI; +using ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Library; + +public partial class SubmissionManagementView : ReactiveUserControl +{ + public SubmissionManagementView() + { + InitializeComponent(); + this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.Screen) + .Subscribe(screen => RouterFrame.NavigateFromObject(screen ?? ViewModel?.DetailsViewModel)) + .DisposeWith(d)); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionManagementViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionManagementViewModel.cs new file mode 100644 index 000000000..af7fd558b --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionManagementViewModel.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Disposables; +using System.Threading; +using System.Threading.Tasks; +using Artemis.UI.Screens.Workshop.Parameters; +using Artemis.UI.Screens.Workshop.SubmissionWizard; +using Artemis.UI.Shared.Routing; +using Artemis.UI.Shared.Services; +using Artemis.WebClient.Workshop; +using Artemis.WebClient.Workshop.Services; +using PropertyChanged.SourceGenerator; +using ReactiveUI; +using StrawberryShake; + +namespace Artemis.UI.Screens.Workshop.Library; + +public partial class SubmissionManagementViewModel : RoutableHostScreen +{ + private readonly IWorkshopClient _client; + private readonly IWindowService _windowService; + private readonly IRouter _router; + private readonly IWorkshopService _workshopService; + + [Notify] private IGetSubmittedEntryById_Entry? _entry; + [Notify] private List? _releases; + [Notify] private IGetSubmittedEntryById_Entry_Releases? _selectedRelease; + + public SubmissionManagementViewModel(IWorkshopClient client, IRouter router, IWindowService windowService, IWorkshopService workshopService, SubmissionDetailsViewModel detailsViewModel) + { + DetailsViewModel = detailsViewModel; + _client = client; + _router = router; + _windowService = windowService; + _workshopService = workshopService; + + this.WhenActivated(d => + { + this.WhenAnyValue(vm => vm.SelectedRelease) + .WhereNotNull() + .Subscribe(r => _router.Navigate($"/releases/{r.Id}")) + .DisposeWith(d); + }); + } + + public SubmissionDetailsViewModel DetailsViewModel { get; } + + public async Task ViewWorkshopPage() + { + if (Entry != null) + await _workshopService.NavigateToEntry(Entry.Id, Entry.EntryType); + } + + public async Task CreateRelease() + { + if (Entry != null) + await _windowService.ShowDialogAsync(Entry); + } + + public async Task DeleteSubmission() + { + if (Entry == null) + return; + + bool confirmed = await _windowService.ShowConfirmContentDialog( + "Delete submission?", + "You cannot undo this by yourself.\r\n" + + "Users that have already downloaded your submission will keep it."); + if (!confirmed) + return; + + IOperationResult result = await _client.RemoveEntry.ExecuteAsync(Entry.Id); + result.EnsureNoErrors(); + await _router.Navigate("workshop/library/submissions"); + } + + public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken) + { + // If there is a 2nd parameter, it's a release ID + SelectedRelease = args.RouteParameters.Length > 1 ? Releases?.FirstOrDefault(r => r.Id == (long) args.RouteParameters[1]) : null; + + IOperationResult result = await _client.GetSubmittedEntryById.ExecuteAsync(parameters.EntryId, cancellationToken); + if (result.IsErrorResult()) + return; + + Entry = result.Data?.Entry; + Releases = Entry?.Releases.OrderByDescending(r => r.CreatedAt).ToList(); + + await DetailsViewModel.SetEntry(Entry, cancellationToken); + } + + public override async Task OnClosing(NavigationArguments args) + { + await DetailsViewModel.OnClosing(args); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml b/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml new file mode 100644 index 000000000..105417bf4 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml @@ -0,0 +1,68 @@ + + + + + + + + + + + Synchronized scrolling + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml.cs new file mode 100644 index 000000000..503c3cb87 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml.cs @@ -0,0 +1,100 @@ +using System.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Media.Immutable; +using Avalonia.ReactiveUI; +using AvaloniaEdit.TextMate; +using ReactiveUI; +using TextMateSharp.Grammars; +using VisualExtensions = Artemis.UI.Shared.Extensions.VisualExtensions; + +namespace Artemis.UI.Screens.Workshop.Library; + +public partial class SubmissionReleaseView : ReactiveUserControl +{ + private ScrollViewer? _editorScrollViewer; + private ScrollViewer? _previewScrollViewer; + private bool _updating; + + public SubmissionReleaseView() + { + InitializeComponent(); + + DescriptionEditor.Options.AllowScrollBelowDocument = false; + RegistryOptions options = new(ThemeName.Dark); + TextMate.Installation? install = DescriptionEditor.InstallTextMate(options); + + install.SetGrammar(options.GetScopeByExtension(".md")); + + this.WhenActivated(_ => SetupScrollSync()); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + if (this.TryFindResource("SystemAccentColorLight3", out object? resource) && resource is Color color) + DescriptionEditor.TextArea.TextView.LinkTextForegroundBrush = new ImmutableSolidColorBrush(color); + + base.OnAttachedToVisualTree(e); + } + + private void SetupScrollSync() + { + if (_editorScrollViewer != null) + _editorScrollViewer.PropertyChanged -= EditorScrollViewerOnPropertyChanged; + if (_previewScrollViewer != null) + _previewScrollViewer.PropertyChanged -= PreviewScrollViewerOnPropertyChanged; + + _editorScrollViewer = VisualExtensions.GetVisualChildrenOfType(DescriptionEditor).FirstOrDefault(); + _previewScrollViewer = VisualExtensions.GetVisualChildrenOfType(DescriptionPreview).FirstOrDefault(); + + if (_editorScrollViewer != null) + _editorScrollViewer.PropertyChanged += EditorScrollViewerOnPropertyChanged; + if (_previewScrollViewer != null) + _previewScrollViewer.PropertyChanged += PreviewScrollViewerOnPropertyChanged; + } + + private void EditorScrollViewerOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property.Name != nameof(ScrollViewer.Offset) || _updating || SynchronizedScrolling.IsChecked != true) + return; + + try + { + _updating = true; + SynchronizeScrollViewers(_editorScrollViewer, _previewScrollViewer); + } + finally + { + _updating = false; + } + } + + private void PreviewScrollViewerOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property.Name != nameof(ScrollViewer.Offset) || _updating || SynchronizedScrolling.IsChecked != true) + return; + + try + { + _updating = true; + SynchronizeScrollViewers(_previewScrollViewer, _editorScrollViewer); + } + finally + { + _updating = false; + } + } + + private void SynchronizeScrollViewers(ScrollViewer? source, ScrollViewer? target) + { + if (source == null || target == null) + return; + + double sourceScrollableHeight = source.Extent.Height - source.Viewport.Height; + double targetScrollableHeight = target.Extent.Height - target.Viewport.Height; + + if (sourceScrollableHeight != 0) + target.Offset = new Vector(target.Offset.X, targetScrollableHeight * (source.Offset.Y / sourceScrollableHeight)); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseViewModel.cs new file mode 100644 index 000000000..eb02f12b6 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseViewModel.cs @@ -0,0 +1,124 @@ +using System; +using System.Reactive; +using System.Reactive.Disposables; +using System.Threading; +using System.Threading.Tasks; +using Artemis.UI.Screens.Workshop.Parameters; +using Artemis.UI.Shared.Routing; +using Artemis.UI.Shared.Services; +using Artemis.UI.Shared.Services.Builders; +using Artemis.WebClient.Workshop; +using Avalonia.Layout; +using AvaloniaEdit.Document; +using PropertyChanged.SourceGenerator; +using ReactiveUI; +using StrawberryShake; + +namespace Artemis.UI.Screens.Workshop.Library; + +public partial class SubmissionReleaseViewModel : RoutableScreen +{ + private readonly IWorkshopClient _client; + private readonly IRouter _router; + private readonly IWindowService _windowService; + private readonly INotificationService _notificationService; + private readonly ObservableAsPropertyHelper _hasChanges; + + [Notify] private IGetReleaseById_Release? _release; + [Notify] private string _changelog = string.Empty; + [Notify] private TextDocument? _markdownDocument; + + public SubmissionReleaseViewModel(IWorkshopClient client, IRouter router, IWindowService windowService, INotificationService notificationService) + { + _client = client; + _router = router; + _windowService = windowService; + _notificationService = notificationService; + _hasChanges = this.WhenAnyValue(vm => vm.Changelog, vm => vm.Release, (current, release) => current != release?.Changelog).ToProperty(this, vm => vm.HasChanges); + + Discard = ReactiveCommand.Create(ExecuteDiscard, this.WhenAnyValue(vm => vm.HasChanges)); + Save = ReactiveCommand.CreateFromTask(ExecuteSave, this.WhenAnyValue(vm => vm.HasChanges)); + + this.WhenActivated(d => + { + Disposable.Create(() => + { + if (MarkdownDocument != null) + MarkdownDocument.TextChanged -= MarkdownDocumentOnTextChanged; + }).DisposeWith(d); + }); + } + + public bool HasChanges => _hasChanges.Value; + public ReactiveCommand Discard { get; set; } + public ReactiveCommand Save { get; set; } + + public override async Task OnNavigating(ReleaseDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken) + { + IOperationResult result = await _client.GetReleaseById.ExecuteAsync(parameters.ReleaseId, cancellationToken); + Release = result.Data?.Release; + Changelog = Release?.Changelog ?? string.Empty; + + SetupMarkdownDocument(); + } + + public async Task DeleteRelease() + { + if (Release == null) + return; + + bool confirmed = await _windowService.ShowConfirmContentDialog( + "Delete release?", + "This cannot be undone.\r\n" + + "Users that have already downloaded this release will keep it."); + if (!confirmed) + return; + + await _client.RemoveRelease.ExecuteAsync(Release.Id); + _notificationService.CreateNotification() + .WithTitle("Deleted release.") + .WithSeverity(NotificationSeverity.Success) + .WithHorizontalPosition(HorizontalAlignment.Left) + .Show(); + + await Close(); + } + + public async Task Close() + { + await _router.GoUp(); + } + + private async Task ExecuteSave(CancellationToken cancellationToken) + { + if (Release == null) + return; + + await _client.UpdateRelease.ExecuteAsync(new UpdateReleaseInput {Id = Release.Id, Changelog = Changelog}, cancellationToken); + _notificationService.CreateNotification() + .WithTitle("Saved changelog.") + .WithSeverity(NotificationSeverity.Success) + .WithHorizontalPosition(HorizontalAlignment.Left) + .Show(); + } + + private void ExecuteDiscard() + { + Changelog = Release?.Changelog ?? string.Empty; + SetupMarkdownDocument(); + } + + private void SetupMarkdownDocument() + { + if (MarkdownDocument != null) + MarkdownDocument.TextChanged -= MarkdownDocumentOnTextChanged; + + MarkdownDocument = new TextDocument(new StringTextSource(Changelog)); + MarkdownDocument.TextChanged += MarkdownDocumentOnTextChanged; + } + + private void MarkdownDocumentOnTextChanged(object? sender, EventArgs e) + { + Changelog = MarkdownDocument?.Text ?? string.Empty; + } +} \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Mutations/UpdateEntry.graphql b/src/Artemis.WebClient.Workshop/Mutations/UpdateEntry.graphql index fe53ec3e5..1a667c53b 100644 --- a/src/Artemis.WebClient.Workshop/Mutations/UpdateEntry.graphql +++ b/src/Artemis.WebClient.Workshop/Mutations/UpdateEntry.graphql @@ -3,3 +3,16 @@ mutation UpdateEntry ($input: UpdateEntryInput!) { id } } + + +mutation UpdateRelease($input: UpdateReleaseInput!) { + updateRelease(input: $input) { + id + } +} + +mutation RemoveRelease($input: Long!) { + removeRelease(id: $input) { + id + } +} \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Queries/GetSubmittedEntryById.graphql b/src/Artemis.WebClient.Workshop/Queries/GetSubmittedEntryById.graphql index 1bcd98fd6..6aece2ca4 100644 --- a/src/Artemis.WebClient.Workshop/Queries/GetSubmittedEntryById.graphql +++ b/src/Artemis.WebClient.Workshop/Queries/GetSubmittedEntryById.graphql @@ -14,5 +14,10 @@ query GetSubmittedEntryById($id: Long!) { images { ...image } + releases { + id + version + createdAt + } } } \ No newline at end of file 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..a8ba99703 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 57c872355..f26e43af8 100644 --- a/src/Artemis.WebClient.Workshop/schema.graphql +++ b/src/Artemis.WebClient.Workshop/schema.graphql @@ -100,6 +100,7 @@ type Mutation { addLayoutInfo(input: CreateLayoutInfoInput!): LayoutInfo removeEntry(id: Long!): Entry removeLayoutInfo(id: Long!): LayoutInfo! + removeRelease(id: Long!): Release! updateEntry(input: UpdateEntryInput!): Entry updateEntryImage(input: UpdateEntryImageInput!): Image updateRelease(input: UpdateReleaseInput!): Release From 62057d657a9416be521d4ad24bc7902080787d61 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 12 Apr 2024 22:53:40 +0200 Subject: [PATCH 10/13] Workshop - Improve child navigation performance --- .../Routing/Routable/IRoutableHostScreen.cs | 1 + .../Routable/RoutableHostScreenOfTScreen.cs | 6 + .../RoutableHostScreenOfTScreenTParam.cs | 6 + .../Routing/Router/Navigation.cs | 7 +- src/Artemis.UI.Shared/Styles/Skeleton.axaml | 10 ++ .../Entries/Details/EntryInfoView.axaml | 154 ++++++++++-------- .../Entries/Details/EntryInfoViewModel.cs | 30 +++- .../EntryReleases/EntryReleasesViewModel.cs | 10 +- .../Workshop/Layout/LayoutDetailsView.axaml | 3 +- .../Layout/LayoutDetailsView.axaml.cs | 3 +- .../Workshop/Layout/LayoutDetailsViewModel.cs | 22 ++- .../Workshop/Layout/LayoutListView.axaml.cs | 3 +- .../Workshop/Layout/LayoutListViewModel.cs | 7 +- .../Library/SubmissionManagementView.axaml | 119 +++++++------- .../Library/SubmissionManagementView.axaml.cs | 3 +- .../Library/SubmissionManagementViewModel.cs | 13 +- .../Workshop/Plugins/PluginDetailsView.axaml | 38 +++-- .../Plugins/PluginDetailsView.axaml.cs | 3 +- .../Plugins/PluginDetailsViewModel.cs | 24 +-- .../Workshop/Plugins/PluginListView.axaml.cs | 3 +- .../Workshop/Plugins/PluginListViewModel.cs | 7 +- .../Workshop/Profile/ProfileDetailsView.axaml | 5 +- .../Profile/ProfileDetailsView.axaml.cs | 3 +- .../Profile/ProfileDetailsViewModel.cs | 22 ++- .../Workshop/Profile/ProfileListView.axaml.cs | 3 +- .../Workshop/Profile/ProfileListViewModel.cs | 7 +- .../Extensions/EntryExtensions.cs | 9 + 27 files changed, 309 insertions(+), 212 deletions(-) create mode 100644 src/Artemis.WebClient.Workshop/Extensions/EntryExtensions.cs diff --git a/src/Artemis.UI.Shared/Routing/Routable/IRoutableHostScreen.cs b/src/Artemis.UI.Shared/Routing/Routable/IRoutableHostScreen.cs index b09afb676..b524f739a 100644 --- a/src/Artemis.UI.Shared/Routing/Routable/IRoutableHostScreen.cs +++ b/src/Artemis.UI.Shared/Routing/Routable/IRoutableHostScreen.cs @@ -9,5 +9,6 @@ internal interface IRoutableHostScreen : IRoutableScreen { bool RecycleScreen { get; } IRoutableScreen? InternalScreen { get; } + IRoutableScreen? InternalDefaultScreen { get; } void InternalChangeScreen(IRoutableScreen? screen); } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Routing/Routable/RoutableHostScreenOfTScreen.cs b/src/Artemis.UI.Shared/Routing/Routable/RoutableHostScreenOfTScreen.cs index c61c27820..ca5208f19 100644 --- a/src/Artemis.UI.Shared/Routing/Routable/RoutableHostScreenOfTScreen.cs +++ b/src/Artemis.UI.Shared/Routing/Routable/RoutableHostScreenOfTScreen.cs @@ -25,7 +25,13 @@ public abstract class RoutableHostScreen : RoutableScreen, IRoutableHos protected set => RaiseAndSetIfChanged(ref _recycleScreen, value); } + /// + /// Gets the screen to show when no other screen is active. + /// + public virtual TScreen? DefaultScreen { get; } + IRoutableScreen? IRoutableHostScreen.InternalScreen => Screen; + IRoutableScreen? IRoutableHostScreen.InternalDefaultScreen => DefaultScreen; void IRoutableHostScreen.InternalChangeScreen(IRoutableScreen? screen) { diff --git a/src/Artemis.UI.Shared/Routing/Routable/RoutableHostScreenOfTScreenTParam.cs b/src/Artemis.UI.Shared/Routing/Routable/RoutableHostScreenOfTScreenTParam.cs index 89773e8d4..1dfef6b76 100644 --- a/src/Artemis.UI.Shared/Routing/Routable/RoutableHostScreenOfTScreenTParam.cs +++ b/src/Artemis.UI.Shared/Routing/Routable/RoutableHostScreenOfTScreenTParam.cs @@ -27,7 +27,13 @@ public abstract class RoutableHostScreen : RoutableScreen RaiseAndSetIfChanged(ref _recycleScreen, value); } + /// + /// Gets the screen to show when no other screen is active. + /// + public virtual TScreen? DefaultScreen { get; } + IRoutableScreen? IRoutableHostScreen.InternalScreen => Screen; + IRoutableScreen? IRoutableHostScreen.InternalDefaultScreen => DefaultScreen; void IRoutableHostScreen.InternalChangeScreen(IRoutableScreen? screen) { diff --git a/src/Artemis.UI.Shared/Routing/Router/Navigation.cs b/src/Artemis.UI.Shared/Routing/Router/Navigation.cs index 0d0f6083c..1df871d34 100644 --- a/src/Artemis.UI.Shared/Routing/Router/Navigation.cs +++ b/src/Artemis.UI.Shared/Routing/Router/Navigation.cs @@ -109,12 +109,11 @@ internal class Navigation // Navigate the child too if (resolution.Child != null) await NavigateResolution(resolution.Child, args, childScreen); - // Make sure there is no child - else if (childScreen.InternalScreen != null) - childScreen.InternalChangeScreen(null); + // Without a resolution, navigate to the default screen (which may be null) + else if (childScreen.InternalScreen != childScreen.InternalDefaultScreen) + childScreen.InternalChangeScreen(childScreen.InternalDefaultScreen); } - Completed = true; } diff --git a/src/Artemis.UI.Shared/Styles/Skeleton.axaml b/src/Artemis.UI.Shared/Styles/Skeleton.axaml index 2596c3fe6..0483a9763 100644 --- a/src/Artemis.UI.Shared/Styles/Skeleton.axaml +++ b/src/Artemis.UI.Shared/Styles/Skeleton.axaml @@ -8,6 +8,7 @@ + TitleTextBlockStyle This is heading 1 This is heading 2 This is heading 3 @@ -22,6 +23,7 @@ + @@ -39,6 +41,7 @@ + TitleTextBlockStyle This is heading 1 This is heading 2 This is heading 3 @@ -51,6 +54,7 @@ + @@ -68,6 +72,7 @@ + TitleTextBlockStyle This is heading 1 This is heading 2 This is heading 3 @@ -125,6 +130,11 @@ + + + + Changelog + + If you want to inform your users what has changed in this release, you can provide a changelog. This is optional but recommended. + + + + + + diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepView.axaml.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepView.axaml.cs new file mode 100644 index 000000000..2733746f7 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps; + +public partial class ChangelogStepView : ReactiveUserControl +{ + public ChangelogStepView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepViewModel.cs new file mode 100644 index 000000000..0333e5a56 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepViewModel.cs @@ -0,0 +1,39 @@ +using System.Reactive.Disposables; +using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout; +using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile; +using Artemis.WebClient.Workshop; +using PropertyChanged.SourceGenerator; +using ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps; + +public partial class ChangelogStepViewModel : SubmissionViewModel +{ + [Notify] private string? _changelog; + + public ChangelogStepViewModel() + { + GoBack = ReactiveCommand.Create(ExecuteGoBack); + Continue = ReactiveCommand.Create(ExecuteContinue); + ContinueText = "Submit"; + + this.WhenActivated((CompositeDisposable _) => Changelog = State.Changelog); + } + + private void ExecuteContinue() + { + State.Changelog = Changelog; + State.ChangeScreen(); + } + + private void ExecuteGoBack() + { + State.Changelog = Changelog; + if (State.EntryType == EntryType.Layout) + State.ChangeScreen(); + else if (State.EntryType == EntryType.Plugin) + State.ChangeScreen(); + else if (State.EntryType == EntryType.Profile) + State.ChangeScreen(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutInfoStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutInfoStepViewModel.cs index 23c3b293e..3ebe3b748 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutInfoStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutInfoStepViewModel.cs @@ -98,6 +98,6 @@ public partial class LayoutInfoStepViewModel : SubmissionViewModel if (State.EntryId == null) State.ChangeScreen(); else - State.ChangeScreen(); + State.ChangeScreen(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Plugin/PluginSelectionStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Plugin/PluginSelectionStepViewModel.cs index 2d8cc45d9..4008c5f57 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Plugin/PluginSelectionStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Plugin/PluginSelectionStepViewModel.cs @@ -78,6 +78,6 @@ public partial class PluginSelectionStepViewModel : SubmissionViewModel if (State.EntryId == null) State.ChangeScreen(); else - State.ChangeScreen(); + State.ChangeScreen(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileAdaptionHintsStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileAdaptionHintsStepViewModel.cs index 0e3556a96..87d7872dc 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileAdaptionHintsStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileAdaptionHintsStepViewModel.cs @@ -64,6 +64,6 @@ public class ProfileAdaptionHintsStepViewModel : SubmissionViewModel if (State.EntryId == null) State.ChangeScreen(); else - State.ChangeScreen(); + State.ChangeScreen(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs index 81e464311..c205e24aa 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs @@ -70,7 +70,7 @@ public partial class UploadStepViewModel : SubmissionViewModel // Create a release for the new entry IEntryUploadHandler uploadHandler = _entryUploadHandlerFactory.CreateHandler(State.EntryType); - EntryUploadResult uploadResult = await uploadHandler.CreateReleaseAsync(_entryId.Value, State.EntrySource!, cancellationToken); + EntryUploadResult uploadResult = await uploadHandler.CreateReleaseAsync(_entryId.Value, State.EntrySource!, State.Changelog, cancellationToken); if (!uploadResult.IsSuccess) throw new ArtemisWorkshopException(uploadResult.Message); diff --git a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/IEntryUploadHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/IEntryUploadHandler.cs index c0bc829be..a25dc689b 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/IEntryUploadHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/IEntryUploadHandler.cs @@ -1,8 +1,6 @@ -using Artemis.UI.Shared.Utilities; - -namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers; +namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers; public interface IEntryUploadHandler { - Task CreateReleaseAsync(long entryId, IEntrySource entrySource, CancellationToken cancellationToken); + Task CreateReleaseAsync(long entryId, IEntrySource entrySource, string? changelog, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs index 38e2c9789..0ed8edfc6 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs @@ -18,7 +18,7 @@ public class LayoutEntryUploadHandler : IEntryUploadHandler } /// - public async Task CreateReleaseAsync(long entryId, IEntrySource entrySource, CancellationToken cancellationToken) + public async Task CreateReleaseAsync(long entryId, IEntrySource entrySource, string? changelog, CancellationToken cancellationToken) { if (entrySource is not LayoutEntrySource source) throw new InvalidOperationException("Can only create releases for layouts"); @@ -62,6 +62,8 @@ public class LayoutEntryUploadHandler : IEntryUploadHandler MultipartFormDataContent content = new(); StreamContent streamContent = new(archiveStream); streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); + if (!string.IsNullOrWhiteSpace(changelog)) + content.Add(new StringContent(changelog), "Changelog"); content.Add(streamContent, "file", "file.zip"); // Submit diff --git a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/PluginEntryUploadHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/PluginEntryUploadHandler.cs index f8c51034c..926f8485b 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/PluginEntryUploadHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/PluginEntryUploadHandler.cs @@ -14,7 +14,7 @@ public class PluginEntryUploadHandler : IEntryUploadHandler } /// - public async Task CreateReleaseAsync(long entryId, IEntrySource entrySource, CancellationToken cancellationToken) + public async Task CreateReleaseAsync(long entryId, IEntrySource entrySource, string? changelog, CancellationToken cancellationToken) { if (entrySource is not PluginEntrySource source) throw new InvalidOperationException("Can only create releases for plugins"); @@ -27,6 +27,8 @@ public class PluginEntryUploadHandler : IEntryUploadHandler MultipartFormDataContent content = new(); StreamContent streamContent = new(fileStream); streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); + if (!string.IsNullOrWhiteSpace(changelog)) + content.Add(new StringContent(changelog), "Changelog"); content.Add(streamContent, "file", "file.zip"); // Submit diff --git a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs index 3dbbd68e9..381bd0199 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs @@ -17,7 +17,7 @@ public class ProfileEntryUploadHandler : IEntryUploadHandler } /// - public async Task CreateReleaseAsync(long entryId, IEntrySource entrySource, CancellationToken cancellationToken) + public async Task CreateReleaseAsync(long entryId, IEntrySource entrySource, string? changelog, CancellationToken cancellationToken) { if (entrySource is not ProfileEntrySource source) throw new InvalidOperationException("Can only create releases for profile configurations"); @@ -32,6 +32,8 @@ public class ProfileEntryUploadHandler : IEntryUploadHandler StreamContent streamContent = new(archiveStream); streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); content.Add(JsonContent.Create(source.Dependencies.Select(d => new {PluginId = d.Plugin.Guid, FeatureId = d.Id}).ToList()), "ReleaseDependencies"); + if (!string.IsNullOrWhiteSpace(changelog)) + content.Add(new StringContent(changelog), "Changelog"); content.Add(streamContent, "file", "file.zip"); // Submit diff --git a/src/Artemis.WebClient.Workshop/WorkshopConstants.cs b/src/Artemis.WebClient.Workshop/WorkshopConstants.cs index 907ddb843..10807064c 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 From 74c24c84ae69acbc5cb91c4965ca55d22991b8b3 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 14 Apr 2024 15:47:29 +0200 Subject: [PATCH 12/13] Workshop - Visual pass --- .../Updating/ReleaseDetailsView.axaml | 2 +- .../Details/EntrySpecificationsViewModel.cs | 13 +-- .../EntryReleases/EntryReleaseView.axaml | 4 +- .../Library/SubmissionDetailsView.axaml | 6 +- .../Library/SubmissionDetailsViewModel.cs | 4 +- .../Library/SubmissionManagementViewModel.cs | 13 ++- .../Library/SubmissionReleaseView.axaml | 89 +++++++++++++++++-- .../Steps/ChangelogStepViewModel.cs | 3 +- 8 files changed, 105 insertions(+), 29 deletions(-) diff --git a/src/Artemis.UI/Screens/Settings/Updating/ReleaseDetailsView.axaml b/src/Artemis.UI/Screens/Settings/Updating/ReleaseDetailsView.axaml index cdb9738aa..be8207516 100644 --- a/src/Artemis.UI/Screens/Settings/Updating/ReleaseDetailsView.axaml +++ b/src/Artemis.UI/Screens/Settings/Updating/ReleaseDetailsView.axaml @@ -43,7 +43,7 @@ - Release info + Release info diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsViewModel.cs index d277f9f62..041af76cc 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsViewModel.cs @@ -64,12 +64,7 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase _categoriesValid = categoriesRule.ValidationChanged.Select(c => c.IsValid).ToProperty(this, vm => vm.CategoriesValid); _descriptionValid = descriptionRule.ValidationChanged.Select(c => c.IsValid).ToProperty(this, vm => vm.DescriptionValid); - this.WhenActivatedAsync(async d => - { - // Load categories - await PopulateCategories(); - Disposable.Create(ClearIcon).DisposeWith(d); - }); + this.WhenActivatedAsync(async _ => await PopulateCategories()); } public ReactiveCommand SelectIcon { get; } @@ -98,12 +93,6 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase IconChanged = true; } - private void ClearIcon() - { - IconBitmap?.Dispose(); - IconBitmap = null; - } - private async Task PopulateCategories() { IOperationResult categories = await _workshopClient.GetCategories.ExecuteAsync(); diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml index 3185f1a05..62a991f55 100644 --- a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleaseView.axaml @@ -42,10 +42,10 @@ - - Release info + Release info - - + + + + diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsViewModel.cs index 1011f8045..7829b7261 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionDetailsViewModel.cs @@ -79,6 +79,8 @@ public partial class SubmissionDetailsViewModel : RoutableScreen bool confirmed = await _windowService.ShowConfirmContentDialog("You have unsaved changes", "Do you want to discard your unsaved changes?"); if (!confirmed) args.Cancel(); + else + await ExecuteDiscardChanges(); } private async Task ApplyDetailsFromEntry(CancellationToken cancellationToken) @@ -94,6 +96,7 @@ public partial class SubmissionDetailsViewModel : RoutableScreen if (Entry == null) { EntrySpecificationsViewModel = null; + ApplyImagesFromEntry(); return; } @@ -176,7 +179,6 @@ public partial class SubmissionDetailsViewModel : RoutableScreen private async Task ExecuteDiscardChanges() { await ApplyDetailsFromEntry(CancellationToken.None); - ApplyImagesFromEntry(); } private async Task ExecuteSaveChanges(CancellationToken cancellationToken) diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionManagementViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionManagementViewModel.cs index 7146f6307..2f7a67147 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/SubmissionManagementViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionManagementViewModel.cs @@ -82,7 +82,18 @@ public partial class SubmissionManagementViewModel : RoutableHostScreen 1 ? Releases?.FirstOrDefault(r => r.Id == (long) args.RouteParameters[1]) : null; - + + // OnNavigating may just be getting called to update the selected release + if (Entry?.Id == parameters.EntryId) + { + // Reapply the entry when closing a release, this is mainly because the entry icon probably got disposed + if (SelectedRelease == null) + await _detailsViewModel.SetEntry(Entry, cancellationToken); + + // No need to reload the entry since it's the same + return; + } + IOperationResult result = await _client.GetSubmittedEntryById.ExecuteAsync(parameters.EntryId, cancellationToken); if (result.IsErrorResult()) return; diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml b/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml index 691e767a1..2040309d0 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml @@ -5,24 +5,95 @@ xmlns:library="clr-namespace:Artemis.UI.Screens.Workshop.Library" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:controls1="clr-namespace:Artemis.UI.Controls" + xmlns:converters="clr-namespace:Artemis.UI.Converters" + xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Workshop.Library.SubmissionReleaseView" x:DataType="library:SubmissionReleaseViewModel"> + + + + + + + + + + + + + - - - - + + + + + Release management + + + + + + + Version + + - + + + Release date + + + + + + File size + + + + + + + + + + Release notes + + + + + - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepViewModel.cs index 0333e5a56..e2e97795f 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepViewModel.cs @@ -1,5 +1,6 @@ using System.Reactive.Disposables; using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout; +using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Plugin; using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile; using Artemis.WebClient.Workshop; using PropertyChanged.SourceGenerator; @@ -32,7 +33,7 @@ public partial class ChangelogStepViewModel : SubmissionViewModel if (State.EntryType == EntryType.Layout) State.ChangeScreen(); else if (State.EntryType == EntryType.Plugin) - State.ChangeScreen(); + State.ChangeScreen(); else if (State.EntryType == EntryType.Profile) State.ChangeScreen(); } From a9d4bd238515615f2e67d1149342baebd4e4ea4d Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 14 Apr 2024 15:59:21 +0200 Subject: [PATCH 13/13] Workshop - Plugin/layout management visual tweaks Workshop - Avoid acidentally pre-selecting a release --- .../Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs | 2 +- src/Artemis.UI/Screens/Workshop/Layout/LayoutManageView.axaml | 2 +- src/Artemis.UI/Screens/Workshop/Plugins/PluginManageView.axaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs index 19eab58ea..a478318e3 100644 --- a/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/EntryReleases/EntryReleasesViewModel.cs @@ -27,7 +27,7 @@ public partial class EntryReleasesViewModel : ActivatableViewModelBase this.WhenActivated(d => { router.CurrentPath.Subscribe(p => - SelectedRelease = p != null && p.StartsWith(Entry.GetEntryPath()) && float.TryParse(p.Split('/').Last(), out float releaseId) + SelectedRelease = p != null && p.StartsWith($"{Entry.GetEntryPath()}/releases") && float.TryParse(p.Split('/').Last(), out float releaseId) ? Releases.FirstOrDefault(r => r.Release.Id == releaseId) : null) .DisposeWith(d); diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutManageView.axaml b/src/Artemis.UI/Screens/Workshop/Layout/LayoutManageView.axaml index af6b25fc2..13be66d55 100644 --- a/src/Artemis.UI/Screens/Workshop/Layout/LayoutManageView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutManageView.axaml @@ -14,7 +14,7 @@ - Manage layout + Manage layout diff --git a/src/Artemis.UI/Screens/Workshop/Plugins/PluginManageView.axaml b/src/Artemis.UI/Screens/Workshop/Plugins/PluginManageView.axaml index c6fe8c059..cf84560af 100644 --- a/src/Artemis.UI/Screens/Workshop/Plugins/PluginManageView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Plugins/PluginManageView.axaml @@ -13,7 +13,7 @@ - Manage plugin + Manage plugin