From 4994b3fb4439a992fe95a8fecfdc99283d818419 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 23 Sep 2023 23:08:28 +0200 Subject: [PATCH] Workshop - Added filtering, sorting and changable entries per page --- .../ApplicationStateManager.cs | 26 ++-- .../Routing/Router/Router.cs | 2 + src/Artemis.UI.Shared/Styles/TextBox.axaml | 53 +++++--- .../ApplicationStateManager.cs | 27 ++-- src/Artemis.UI/Screens/Root/RootViewModel.cs | 25 ++-- .../VisualScripting/NodePickerView.axaml | 23 +--- .../Workshop/Entries/EntryListInputView.axaml | 42 +++++++ .../Entries/EntryListInputView.axaml.cs | 13 ++ .../Entries/EntryListInputViewModel.cs | 73 +++++++++++ .../Workshop/Entries/EntryListViewModel.cs | 118 ++++++++++++------ .../Entries/Tabs/LayoutListView.axaml | 42 +++++-- .../Entries/Tabs/LayoutListViewModel.cs | 18 +-- .../Entries/Tabs/ProfileListView.axaml | 45 +++++-- .../Entries/Tabs/ProfileListViewModel.cs | 18 +-- .../Screens/Workshop/Search/SearchView.axaml | 1 - .../Screens/Workshop/WorkshopViewModel.cs | 23 +++- .../Queries/GetEntries.graphql | 4 +- src/Artemis.WebClient.Workshop/schema.graphql | 2 +- 18 files changed, 396 insertions(+), 159 deletions(-) create mode 100644 src/Artemis.UI/Screens/Workshop/Entries/EntryListInputView.axaml create mode 100644 src/Artemis.UI/Screens/Workshop/Entries/EntryListInputView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Entries/EntryListInputViewModel.cs diff --git a/src/Artemis.UI.Linux/ApplicationStateManager.cs b/src/Artemis.UI.Linux/ApplicationStateManager.cs index e580f5fdf..4d3b1e2cd 100644 --- a/src/Artemis.UI.Linux/ApplicationStateManager.cs +++ b/src/Artemis.UI.Linux/ApplicationStateManager.cs @@ -12,18 +12,24 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Threading; using DryIoc; +using Serilog; namespace Artemis.UI.Linux; public class ApplicationStateManager { + private readonly IContainer _container; + private readonly ILogger _logger; private readonly IWindowService _windowService; + // ReSharper disable once NotAccessedField.Local - Kept in scope to ensure it does not get released private Mutex? _artemisMutex; public ApplicationStateManager(IContainer container, string[] startupArguments) { + _container = container; + _logger = container.Resolve(); _windowService = container.Resolve(); StartupArguments = startupArguments; @@ -33,14 +39,7 @@ public class ApplicationStateManager // On OS shutdown dispose the IOC container just so device providers get a chance to clean up if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime) - controlledApplicationLifetime.Exit += (_, _) => - { - RunForcedShutdownIfEnabled(); - - // Dispose plugins before disposing the IOC container because plugins might access services during dispose - container.Resolve().Dispose(); - container.Dispose(); - }; + controlledApplicationLifetime.Exit += ControlledApplicationLifetimeOnExit; } public string[] StartupArguments { get; } @@ -124,6 +123,17 @@ public class ApplicationStateManager Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown()); } + private void ControlledApplicationLifetimeOnExit(object? sender, ControlledApplicationLifetimeExitEventArgs e) + { + _logger.Information("Application lifetime exiting, disposing container and friends"); + + RunForcedShutdownIfEnabled(); + + // Dispose plugins before disposing the IOC container because plugins might access services during dispose + _container.Resolve().Dispose(); + _container.Dispose(); + } + private void UtilitiesOnShutdownRequested(object? sender, EventArgs e) { RunForcedShutdownIfEnabled(); diff --git a/src/Artemis.UI.Shared/Routing/Router/Router.cs b/src/Artemis.UI.Shared/Routing/Router/Router.cs index 282511c40..540cf25e8 100644 --- a/src/Artemis.UI.Shared/Routing/Router/Router.cs +++ b/src/Artemis.UI.Shared/Routing/Router/Router.cs @@ -177,6 +177,8 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable _currentRouteSubject.Dispose(); _mainWindowService.MainWindowOpened -= MainWindowServiceOnMainWindowOpened; _mainWindowService.MainWindowClosed -= MainWindowServiceOnMainWindowClosed; + + _logger.Debug("Router disposed, should that be? Stacktrace: \r\n{StackTrace}", Environment.StackTrace); } private void MainWindowServiceOnMainWindowOpened(object? sender, EventArgs e) diff --git a/src/Artemis.UI.Shared/Styles/TextBox.axaml b/src/Artemis.UI.Shared/Styles/TextBox.axaml index dd9039a05..da7c28175 100644 --- a/src/Artemis.UI.Shared/Styles/TextBox.axaml +++ b/src/Artemis.UI.Shared/Styles/TextBox.axaml @@ -45,10 +45,10 @@ - + + Content="{TemplateBinding InnerLeftContent}" /> + VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> + Text="{TemplateBinding Text, Mode=TwoWay}" + CaretIndex="{TemplateBinding CaretIndex}" + SelectionStart="{TemplateBinding SelectionStart}" + SelectionEnd="{TemplateBinding SelectionEnd}" + TextAlignment="{TemplateBinding TextAlignment}" + TextWrapping="{TemplateBinding TextWrapping}" + LineHeight="{TemplateBinding LineHeight}" + PasswordChar="{TemplateBinding PasswordChar}" + RevealPassword="{TemplateBinding RevealPassword}" + SelectionBrush="{TemplateBinding SelectionBrush}" + SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}" + CaretBrush="{TemplateBinding CaretBrush}" + HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> @@ -137,4 +137,23 @@ + \ No newline at end of file diff --git a/src/Artemis.UI.Windows/ApplicationStateManager.cs b/src/Artemis.UI.Windows/ApplicationStateManager.cs index 2d13fe49e..5eb2f93bd 100644 --- a/src/Artemis.UI.Windows/ApplicationStateManager.cs +++ b/src/Artemis.UI.Windows/ApplicationStateManager.cs @@ -11,15 +11,21 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Threading; using DryIoc; +using Serilog; namespace Artemis.UI.Windows; public class ApplicationStateManager { + private readonly IContainer _container; + private readonly ILogger _logger; private const int SM_SHUTTINGDOWN = 0x2000; public ApplicationStateManager(IContainer container, string[] startupArguments) { + _container = container; + _logger = container.Resolve(); + StartupArguments = startupArguments; IsElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); @@ -29,20 +35,14 @@ public class ApplicationStateManager // On Windows shutdown dispose the IOC container just so device providers get a chance to clean up if (Application.Current?.ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime) - controlledApplicationLifetime.Exit += (_, _) => - { - RunForcedShutdownIfEnabled(); - - // Dispose plugins before disposing the IOC container because plugins might access services during dispose - container.Resolve().Dispose(); - container.Dispose(); - }; + controlledApplicationLifetime.Exit += ControlledApplicationLifetimeOnExit; // Inform the Core about elevation status container.Resolve().IsElevated = IsElevated; } public string[] StartupArguments { get; } + public bool IsElevated { get; } private void UtilitiesOnRestartRequested(object? sender, RestartEventArgs e) @@ -111,6 +111,17 @@ public class ApplicationStateManager Dispatcher.UIThread.Post(() => controlledApplicationLifetime.Shutdown()); } + private void ControlledApplicationLifetimeOnExit(object? sender, ControlledApplicationLifetimeExitEventArgs e) + { + _logger.Information("Application lifetime exiting, disposing container and friends"); + + RunForcedShutdownIfEnabled(); + + // Dispose plugins before disposing the IOC container because plugins might access services during dispose + _container.Resolve().Dispose(); + _container.Dispose(); + } + private void UtilitiesOnShutdownRequested(object? sender, EventArgs e) { // Use PowerShell to kill the process after 8 sec just in case diff --git a/src/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Artemis.UI/Screens/Root/RootViewModel.cs index 8b319b300..4f6580793 100644 --- a/src/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Artemis.UI/Screens/Root/RootViewModel.cs @@ -1,5 +1,7 @@ using System; using System.Reactive; +using System.Reactive.Disposables; +using System.Reactive.Linq; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; @@ -28,7 +30,7 @@ public class RootViewModel : RoutableHostScreen, IMainWindowProv private readonly ISettingsService _settingsService; private readonly IUpdateService _updateService; private readonly IWindowService _windowService; - private ViewModelBase? _titleBarViewModel; + private readonly ObservableAsPropertyHelper _titleBarViewModel; public RootViewModel(IRouter router, ICoreService coreService, @@ -61,7 +63,13 @@ public class RootViewModel : RoutableHostScreen, IMainWindowProv OpenScreen = ReactiveCommand.Create(ExecuteOpenScreen); OpenDebugger = ReactiveCommand.CreateFromTask(ExecuteOpenDebugger); Exit = ReactiveCommand.CreateFromTask(ExecuteExit); - this.WhenAnyValue(vm => vm.Screen).Subscribe(UpdateTitleBarViewModel); + + _titleBarViewModel = this.WhenAnyValue(vm => vm.Screen) + .Select(s => s as IMainScreenViewModel) + .Select(s => s?.WhenAnyValue(svm => svm.TitleBarViewModel) ?? Observable.Never()) + .Switch() + .Select(vm => vm ?? _defaultTitleBarViewModel) + .ToProperty(this, vm => vm.TitleBarViewModel); Task.Run(() => { @@ -82,12 +90,7 @@ public class RootViewModel : RoutableHostScreen, IMainWindowProv public ReactiveCommand OpenDebugger { get; } public ReactiveCommand Exit { get; } - public ViewModelBase? TitleBarViewModel - { - get => _titleBarViewModel; - set => RaiseAndSetIfChanged(ref _titleBarViewModel, value); - } - + public ViewModelBase? TitleBarViewModel => _titleBarViewModel.Value; public static PluginSetting? WindowSizeSetting { get; private set; } public void GoBack() @@ -100,12 +103,6 @@ public class RootViewModel : RoutableHostScreen, IMainWindowProv _router.GoForward(); } - private void UpdateTitleBarViewModel(RoutableScreen? viewModel) - { - IMainScreenViewModel? mainScreenViewModel = viewModel as IMainScreenViewModel; - TitleBarViewModel = mainScreenViewModel?.TitleBarViewModel ?? _defaultTitleBarViewModel; - } - private void CurrentMainWindowOnClosing(object? sender, EventArgs e) { WindowSizeSetting?.Save(); diff --git a/src/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml b/src/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml index 8fe19c727..1e7ac99cc 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml +++ b/src/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml @@ -10,30 +10,9 @@ x:DataType="visualScripting:NodePickerViewModel" Width="600" Height="400"> - - - - + + + + + + + + + + + + Sort by + + Recently updated + Recently added + Download count + + + + Show per page + + 10 + 20 + 50 + 100 + + + + + + + + + diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntryListInputView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntryListInputView.axaml.cs new file mode 100644 index 000000000..650d82eb9 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntryListInputView.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Artemis.UI.Screens.Workshop.Entries; + +public partial class EntryListInputView : UserControl +{ + public EntryListInputView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntryListInputViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntryListInputViewModel.cs new file mode 100644 index 000000000..0d2b8b091 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntryListInputViewModel.cs @@ -0,0 +1,73 @@ +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.UI.Shared; +using ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Entries; + +public class EntryListInputViewModel : ViewModelBase +{ + private static string? _lastSearch; + private readonly PluginSetting _entriesPerPage; + private readonly PluginSetting _sortBy; + private string? _search; + private string _searchWatermark = "Search"; + private int _totalCount; + + public EntryListInputViewModel(ISettingsService settingsService) + { + _search = _lastSearch; + _entriesPerPage = settingsService.GetSetting("Workshop.EntriesPerPage", 10); + _sortBy = settingsService.GetSetting("Workshop.SortBy", 10); + _entriesPerPage.AutoSave = true; + _sortBy.AutoSave = true; + } + + public string SearchWatermark + { + get => _searchWatermark; + set => RaiseAndSetIfChanged(ref _searchWatermark, value); + } + + public string? Search + { + get => _search; + set + { + RaiseAndSetIfChanged(ref _search, value); + _lastSearch = value; + } + } + + public int EntriesPerPage + { + get => _entriesPerPage.Value; + set + { + _entriesPerPage.Value = value; + this.RaisePropertyChanged(); + } + } + + public int SortBy + { + get => _sortBy.Value; + set + { + _sortBy.Value = value; + this.RaisePropertyChanged(); + } + } + + public int TotalCount + { + get => _totalCount; + set => RaiseAndSetIfChanged(ref _totalCount, value); + } + + public void ClearLastSearch() + { + _lastSearch = null; + } + +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntryListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntryListViewModel.cs index ff4d2a138..d1e672088 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/EntryListViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntryListViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Reactive.Disposables; using System.Reactive.Linq; @@ -6,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using Artemis.UI.Screens.Workshop.Categories; using Artemis.UI.Screens.Workshop.Parameters; -using Artemis.UI.Shared; using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services.Builders; @@ -20,26 +20,33 @@ namespace Artemis.UI.Screens.Workshop.Entries; public abstract class EntryListViewModel : RoutableScreen { - private readonly INotificationService _notificationService; - private readonly IWorkshopClient _workshopClient; - private readonly ObservableAsPropertyHelper _showPagination; - private readonly ObservableAsPropertyHelper _isLoading; private readonly SourceList _entries = new(); - - private int _page; + private readonly ObservableAsPropertyHelper _isLoading; + private readonly INotificationService _notificationService; + private readonly string _route; + private readonly ObservableAsPropertyHelper _showPagination; + private readonly IWorkshopClient _workshopClient; private int _loadedPage = -1; - private int _totalPages = 1; - private int _entriesPerPage = 10; - protected EntryListViewModel(IWorkshopClient workshopClient, IRouter router, CategoriesViewModel categoriesViewModel, INotificationService notificationService, + private int _page; + private int _totalPages = 1; + + protected EntryListViewModel(string route, + IWorkshopClient workshopClient, + IRouter router, + CategoriesViewModel categoriesViewModel, + EntryListInputViewModel entryListInputViewModel, + INotificationService notificationService, Func getEntryListViewModel) { + _route = route; _workshopClient = workshopClient; _notificationService = notificationService; _showPagination = this.WhenAnyValue(vm => vm.TotalPages).Select(t => t > 1).ToProperty(this, vm => vm.ShowPagination); _isLoading = this.WhenAnyValue(vm => vm.Page, vm => vm.LoadedPage, (p, c) => p != c).ToProperty(this, vm => vm.IsLoading); CategoriesViewModel = categoriesViewModel; + InputViewModel = entryListInputViewModel; _entries.Connect() .ObserveOn(new AvaloniaSynchronizationContext(DispatcherPriority.SystemIdle)) @@ -49,26 +56,22 @@ public abstract class EntryListViewModel : RoutableScreen vm.Page).Skip(1).Subscribe(p => Task.Run(() => router.Navigate(GetPagePath(p)))); + this.WhenAnyValue(vm => vm.Page).Skip(1).Subscribe(p => Task.Run(() => router.Navigate($"{_route}/{p}"))); - // Respond to filter changes - this.WhenActivated(d => CategoriesViewModel.WhenAnyValue(vm => vm.CategoryFilters).Skip(1).Subscribe(_ => + this.WhenActivated(d => { - // Reset to page one, will trigger a query - if (Page != 1) - Page = 1; - // If already at page one, force a query - else - Task.Run(() => Query(CancellationToken.None)); - }).DisposeWith(d)); + // Respond to filter query input changes + InputViewModel.WhenAnyValue(vm => vm.Search).Skip(1).Throttle(TimeSpan.FromMilliseconds(200)).Subscribe(_ => RefreshToStart()).DisposeWith(d); + InputViewModel.WhenAnyValue(vm => vm.SortBy, vm => vm.EntriesPerPage).Skip(1).Subscribe(_ => RefreshToStart()).DisposeWith(d); + CategoriesViewModel.WhenAnyValue(vm => vm.CategoryFilters).Skip(1).Subscribe(_ => RefreshToStart()).DisposeWith(d); + }); } - protected abstract string GetPagePath(int page); - public bool ShowPagination => _showPagination.Value; public bool IsLoading => _isLoading.Value; public CategoriesViewModel CategoriesViewModel { get; } + public EntryListInputViewModel InputViewModel { get; } public ReadOnlyObservableCollection Entries { get; } @@ -84,18 +87,13 @@ public abstract class EntryListViewModel : RoutableScreen RaiseAndSetIfChanged(ref _loadedPage, value); } + public int TotalPages { get => _totalPages; set => RaiseAndSetIfChanged(ref _totalPages, value); } - public int EntriesPerPage - { - get => _entriesPerPage; - set => RaiseAndSetIfChanged(ref _entriesPerPage, value); - } - public override async Task OnNavigating(WorkshopListParameters parameters, NavigationArguments args, CancellationToken cancellationToken) { Page = Math.Max(1, parameters.Page); @@ -105,17 +103,70 @@ public abstract class EntryListViewModel : RoutableScreen GetSort() + { + // Sort by created at + if (InputViewModel.SortBy == 1) + return new[] {new EntrySortInput {CreatedAt = SortEnumType.Desc}}; + + // Sort by downloads + if (InputViewModel.SortBy == 2) + return new[] {new EntrySortInput {Downloads = SortEnumType.Desc}}; + + + // Sort by latest release, then by created at + return new[] + { + new EntrySortInput {LatestRelease = new ReleaseSortInput {CreatedAt = SortEnumType.Desc}}, + new EntrySortInput {CreatedAt = SortEnumType.Desc} + }; + } + + private void RefreshToStart() + { + // Reset to page one, will trigger a query + if (Page != 1) + Page = 1; + // If already at page one, force a query + else + Task.Run(() => Query(CancellationToken.None)); + } + private async Task Query(CancellationToken cancellationToken) { try { + string? search = string.IsNullOrWhiteSpace(InputViewModel.Search) ? null : InputViewModel.Search; EntryFilterInput filter = GetFilter(); - IOperationResult entries = await _workshopClient.GetEntries.ExecuteAsync(filter, EntriesPerPage * (Page - 1), EntriesPerPage, cancellationToken); + IReadOnlyList sort = GetSort(); + IOperationResult entries = await _workshopClient.GetEntries.ExecuteAsync( + search, + filter, + InputViewModel.EntriesPerPage * (Page - 1), + InputViewModel.EntriesPerPage, + sort, + cancellationToken + ); entries.EnsureNoErrors(); if (entries.Data?.Entries?.Items != null) { - TotalPages = (int) Math.Ceiling(entries.Data.Entries.TotalCount / (double) EntriesPerPage); + TotalPages = (int) Math.Ceiling(entries.Data.Entries.TotalCount / (double) InputViewModel.EntriesPerPage); + InputViewModel.TotalCount = entries.Data.Entries.TotalCount; _entries.Edit(e => { e.Clear(); @@ -123,7 +174,9 @@ public abstract class EntryListViewModel : RoutableScreen - - + + + + + + + Categories @@ -20,18 +27,35 @@ - - - + + + + + + + + + + - - + + + + + + Looks like your current filters gave no results + + Modify or clear your filters to view other device layouts + + + + public LayoutListViewModel(IWorkshopClient workshopClient, IRouter router, CategoriesViewModel categoriesViewModel, + EntryListInputViewModel entryListInputViewModel, INotificationService notificationService, Func getEntryListViewModel) - : base(workshopClient, router, categoriesViewModel, notificationService, getEntryListViewModel) + : base("workshop/entries/layout", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel) { + entryListInputViewModel.SearchWatermark = "Search layouts"; } - #region Overrides of EntryListBaseViewModel - - /// - protected override string GetPagePath(int page) - { - return $"workshop/entries/layouts/{page}"; - } - - /// protected override EntryFilterInput GetFilter() { return new EntryFilterInput { And = new[] { - new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = WebClient.Workshop.EntryType.Layout}}, + new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = EntryType.Layout}}, base.GetFilter() } }; } - - #endregion } \ 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 index e7b733741..cd6e6641b 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml @@ -2,14 +2,21 @@ 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:profile="clr-namespace:Artemis.UI.Screens.Workshop.Profile" xmlns:pagination="clr-namespace:Artemis.UI.Shared.Pagination;assembly=Artemis.UI.Shared" xmlns:tabs="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Tabs" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Workshop.Entries.Tabs.ProfileListView" x:DataType="tabs:ProfileListViewModel"> - - + + + + + + + Categories @@ -20,19 +27,35 @@ - - - - + + + + + + + + + + - - + + + + + + Looks like your current filters gave no results + + Modify or clear your filters to view some awesome profiles + + + + public ProfileListViewModel(IWorkshopClient workshopClient, IRouter router, CategoriesViewModel categoriesViewModel, + EntryListInputViewModel entryListInputViewModel, INotificationService notificationService, Func getEntryListViewModel) - : base(workshopClient, router, categoriesViewModel, notificationService, getEntryListViewModel) + : base("workshop/entries/profiles", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel) { + entryListInputViewModel.SearchWatermark = "Search profiles"; } - #region Overrides of EntryListBaseViewModel - - /// - protected override string GetPagePath(int page) - { - return $"workshop/entries/profiles/{page}"; - } - - /// protected override EntryFilterInput GetFilter() { return new EntryFilterInput { And = new[] { - new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = WebClient.Workshop.EntryType.Profile}}, + new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = EntryType.Profile}}, base.GetFilter() } }; } - - #endregion } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml b/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml index c66d738c1..f1650d9a6 100644 --- a/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml @@ -3,7 +3,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:search="clr-namespace:Artemis.UI.Screens.Workshop.Search" - xmlns:workshop="clr-namespace:Artemis.WebClient.Workshop;assembly=Artemis.WebClient.Workshop" xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" diff --git a/src/Artemis.UI/Screens/Workshop/WorkshopViewModel.cs b/src/Artemis.UI/Screens/Workshop/WorkshopViewModel.cs index aa6fc9eb2..96825dcdd 100644 --- a/src/Artemis.UI/Screens/Workshop/WorkshopViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/WorkshopViewModel.cs @@ -1,4 +1,6 @@ -using Artemis.UI.Screens.Workshop.Home; +using System.Threading; +using System.Threading.Tasks; +using Artemis.UI.Screens.Workshop.Home; using Artemis.UI.Screens.Workshop.Search; using Artemis.UI.Shared; using Artemis.UI.Shared.Routing; @@ -7,12 +9,27 @@ namespace Artemis.UI.Screens.Workshop; public class WorkshopViewModel : RoutableHostScreen, IMainScreenViewModel { + private readonly SearchViewModel _searchViewModel; + private ViewModelBase? _titleBarViewModel; + public WorkshopViewModel(SearchViewModel searchViewModel, WorkshopHomeViewModel homeViewModel) { - TitleBarViewModel = searchViewModel; + _searchViewModel = searchViewModel; HomeViewModel = homeViewModel; } - public ViewModelBase TitleBarViewModel { get; } public WorkshopHomeViewModel HomeViewModel { get; } + + /// + public override Task OnNavigating(NavigationArguments args, CancellationToken cancellationToken) + { + TitleBarViewModel = args.Path == "workshop" ? _searchViewModel : null; + return base.OnNavigating(args, cancellationToken); + } + + public ViewModelBase? TitleBarViewModel + { + get => _titleBarViewModel; + set => RaiseAndSetIfChanged(ref _titleBarViewModel, value); + } } \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql b/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql index e5e0648be..c6223a835 100644 --- a/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql +++ b/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql @@ -1,5 +1,5 @@ -query GetEntries($filter: EntryFilterInput $skip: Int $take: Int) { - entries(where: $filter skip: $skip take: $take, order: {createdAt: DESC}) { +query GetEntries($search: String $filter: EntryFilterInput $skip: Int $take: Int $order: [EntrySortInput!]) { + entries(search: $search where: $filter skip: $skip take: $take, order: $order) { totalCount items { id diff --git a/src/Artemis.WebClient.Workshop/schema.graphql b/src/Artemis.WebClient.Workshop/schema.graphql index c889bca85..29e471408 100644 --- a/src/Artemis.WebClient.Workshop/schema.graphql +++ b/src/Artemis.WebClient.Workshop/schema.graphql @@ -61,7 +61,7 @@ type Mutation { type Query { categories(order: [CategorySortInput!], where: CategoryFilterInput): [Category!]! - entries(order: [EntrySortInput!], skip: Int, take: Int, where: EntryFilterInput): EntriesCollectionSegment + entries(order: [EntrySortInput!], search: String, skip: Int, take: Int, where: EntryFilterInput): EntriesCollectionSegment entry(id: Long!): Entry searchEntries(input: String!, order: [EntrySortInput!], type: EntryType, where: EntryFilterInput): [Entry!]! submittedEntries(order: [EntrySortInput!], where: EntryFilterInput): [Entry!]!