From 07d4539add289572b73bad68657c9da90e47b7d4 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 14 Jul 2023 23:05:44 +0200 Subject: [PATCH] Workshop list progress --- src/Artemis.UI.Shared/Styles/Sidebar.axaml | 2 +- src/Artemis.UI.Shared/Styles/TextBlock.axaml | 31 ++++++++++ src/Artemis.UI/Artemis.UI.csproj | 7 +++ .../Categories/CategoriesViewModel.cs | 24 +++++--- .../CurrentUser/CurrentUserView.axaml | 6 +- .../CurrentUser/CurrentUserViewModel.cs | 55 +++++++++++------ .../Workshop/Entries/EntryListView.axaml | 61 +++++++++++++++++++ .../Workshop/Entries/EntryListView.axaml.cs | 24 ++++++++ .../Workshop/Entries/EntryListViewModel.cs | 37 +++++++++++ .../Profile/ProfileListEntryView.axaml | 39 ------------ .../Profile/ProfileListEntryView.axaml.cs | 18 ------ .../Profile/ProfileListEntryViewModel.cs | 14 ----- .../Workshop/Profile/ProfileListViewModel.cs | 29 +++++++-- .../Screens/Workshop/Search/SearchView.axaml | 4 +- .../Workshop/Search/SearchViewModel.cs | 25 ++------ src/Artemis.UI/Styles/TreeView.axaml | 3 +- .../Queries/GetEntries.graphql | 3 + .../Queries/SearchEntries.graphql | 21 +++---- 18 files changed, 258 insertions(+), 145 deletions(-) create mode 100644 src/Artemis.UI/Screens/Workshop/Entries/EntryListView.axaml create mode 100644 src/Artemis.UI/Screens/Workshop/Entries/EntryListView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/Entries/EntryListViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Workshop/Profile/ProfileListEntryView.axaml delete mode 100644 src/Artemis.UI/Screens/Workshop/Profile/ProfileListEntryView.axaml.cs delete mode 100644 src/Artemis.UI/Screens/Workshop/Profile/ProfileListEntryViewModel.cs diff --git a/src/Artemis.UI.Shared/Styles/Sidebar.axaml b/src/Artemis.UI.Shared/Styles/Sidebar.axaml index 605ba8e40..5fbfb48d5 100644 --- a/src/Artemis.UI.Shared/Styles/Sidebar.axaml +++ b/src/Artemis.UI.Shared/Styles/Sidebar.axaml @@ -22,6 +22,6 @@ \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Styles/TextBlock.axaml b/src/Artemis.UI.Shared/Styles/TextBlock.axaml index 9d3cf6ab4..8a5099cde 100644 --- a/src/Artemis.UI.Shared/Styles/TextBlock.axaml +++ b/src/Artemis.UI.Shared/Styles/TextBlock.axaml @@ -10,6 +10,15 @@ This is heading 5 This is heading 6 This is a subtitle + + This is heading 1 + This is heading 2 + This is heading 3 + This is heading 4 + This is heading 5 + This is heading 6 + This is a subtitle + @@ -50,4 +59,26 @@ + + + + + + + + diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 4e71ac252..5da497f13 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -55,4 +55,11 @@ + + + + ProfileListEntryView.axaml + Code + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Categories/CategoriesViewModel.cs b/src/Artemis.UI/Screens/Workshop/Categories/CategoriesViewModel.cs index f131958ae..e3b1e0464 100644 --- a/src/Artemis.UI/Screens/Workshop/Categories/CategoriesViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Categories/CategoriesViewModel.cs @@ -1,11 +1,12 @@ using System; -using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; using Artemis.UI.Shared; using Artemis.WebClient.Workshop; +using DynamicData; using ReactiveUI; using Serilog; using StrawberryShake; @@ -16,21 +17,21 @@ public class CategoriesViewModel : ActivatableViewModelBase { private readonly IWorkshopClient _client; private readonly ILogger _logger; - private IReadOnlyList _categories; + public readonly SourceList _categories; public CategoriesViewModel(ILogger logger, IWorkshopClient client) { _logger = logger; _client = client; - + _categories = new SourceList(); + _categories.Connect().Bind(out ReadOnlyObservableCollection categoryViewModels).Subscribe(); + + Categories = categoryViewModels; this.WhenActivated(d => ReactiveCommand.CreateFromTask(GetCategories).Execute().Subscribe().DisposeWith(d)); } - public IReadOnlyList Categories - { - get => _categories; - set => RaiseAndSetIfChanged(ref _categories, value); - } + public ReadOnlyObservableCollection Categories { get; } + private async Task GetCategories(CancellationToken cancellationToken) { @@ -40,7 +41,12 @@ public class CategoriesViewModel : ActivatableViewModelBase if (result.IsErrorResult()) _logger.Warning("Failed to retrieve categories {Error}", result.Errors); - Categories = result.Data?.Categories.Select(c => new CategoryViewModel(c)).ToList() ?? new List(); + _categories.Edit(l => + { + l.Clear(); + if (result.Data?.Categories != null) + l.AddRange(result.Data.Categories.Select(c => new CategoryViewModel(c))); + }); } catch (Exception e) { diff --git a/src/Artemis.UI/Screens/Workshop/CurrentUser/CurrentUserView.axaml b/src/Artemis.UI/Screens/Workshop/CurrentUser/CurrentUserView.axaml index 57a245c81..9487ac4eb 100644 --- a/src/Artemis.UI/Screens/Workshop/CurrentUser/CurrentUserView.axaml +++ b/src/Artemis.UI/Screens/Workshop/CurrentUser/CurrentUserView.axaml @@ -9,9 +9,9 @@ x:Class="Artemis.UI.Screens.Workshop.CurrentUser.CurrentUserView" x:DataType="currentUser:CurrentUserViewModel"> - + - + @@ -27,7 +27,7 @@ - + diff --git a/src/Artemis.UI/Screens/Workshop/CurrentUser/CurrentUserViewModel.cs b/src/Artemis.UI/Screens/Workshop/CurrentUser/CurrentUserViewModel.cs index 7df2e3150..c204c3165 100644 --- a/src/Artemis.UI/Screens/Workshop/CurrentUser/CurrentUserViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/CurrentUser/CurrentUserViewModel.cs @@ -10,38 +10,33 @@ using Artemis.WebClient.Workshop.Services; using Avalonia.Media.Imaging; using Flurl.Http; using ReactiveUI; +using Serilog; namespace Artemis.UI.Screens.Workshop.CurrentUser; public class CurrentUserViewModel : ActivatableViewModelBase { + private readonly ILogger _logger; private readonly IAuthenticationService _authenticationService; - private ObservableAsPropertyHelper? _isLoggedIn; - - private string? _userId; - private string? _name; - private string? _email; + private bool _loading = true; private Bitmap? _avatar; + private string? _email; + private string? _name; + private string? _userId; - public CurrentUserViewModel(IAuthenticationService authenticationService) + public CurrentUserViewModel(ILogger logger, IAuthenticationService authenticationService) { + _logger = logger; _authenticationService = authenticationService; Login = ReactiveCommand.CreateFromTask(ExecuteLogin); - this.WhenActivated(d => _isLoggedIn = _authenticationService.WhenAnyValue(s => s.IsLoggedIn).ToProperty(this, vm => vm.IsLoggedIn).DisposeWith(d)); - this.WhenActivated(d => - { - Task.Run(async () => - { - await _authenticationService.AutoLogin(); - await LoadCurrentUser(); - }).DisposeWith(d); - }); + this.WhenActivated(d => ReactiveCommand.CreateFromTask(ExecuteAutoLogin).Execute().Subscribe().DisposeWith(d)); } - public void Logout() + public bool Loading { - _authenticationService.Logout(); + get => _loading; + set => RaiseAndSetIfChanged(ref _loading, value); } public string? UserId @@ -69,18 +64,38 @@ public class CurrentUserViewModel : ActivatableViewModelBase } public ReactiveCommand Login { get; } - public bool IsLoggedIn => _isLoggedIn?.Value ?? false; + + public void Logout() + { + _authenticationService.Logout(); + } private async Task ExecuteLogin(CancellationToken cancellationToken) { await _authenticationService.Login(); await LoadCurrentUser(); - Console.WriteLine(_authenticationService.Claims); + } + + private async Task ExecuteAutoLogin(CancellationToken cancellationToken) + { + try + { + await _authenticationService.AutoLogin(); + await LoadCurrentUser(); + } + catch (Exception e) + { + _logger.Warning(e, "Failed to load the current user"); + } + finally + { + Loading = false; + } } private async Task LoadCurrentUser() { - if (!IsLoggedIn) + if (!_authenticationService.IsLoggedIn) return; UserId = _authenticationService.Claims.FirstOrDefault(c => c.Type == "sub")?.Value; diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntryListView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/EntryListView.axaml new file mode 100644 index 000000000..e7450f685 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntryListView.axaml @@ -0,0 +1,61 @@ + + + + + + + + + + + + by + + + + + + + + + + + + + + + + + + + + + + + + + + downloads + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntryListView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntryListView.axaml.cs new file mode 100644 index 000000000..42a7b86ad --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntryListView.axaml.cs @@ -0,0 +1,24 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.Entries; + +public partial class EntryListView : ReactiveUserControl +{ + public EntryListView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private async void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e) + { + await ViewModel.NavigateToEntry(); + } +} \ 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 new file mode 100644 index 000000000..7a318fc27 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntryListViewModel.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; +using Artemis.UI.Shared; +using Artemis.UI.Shared.Routing; +using Artemis.WebClient.Workshop; + +namespace Artemis.UI.Screens.Workshop.Entries; + +public class EntryListViewModel : ViewModelBase +{ + private readonly IRouter _router; + + public EntryListViewModel(IGetEntries_Entries_Nodes entry, IRouter router) + { + _router = router; + Entry = entry; + } + + public IGetEntries_Entries_Nodes Entry { get; } + + public async Task NavigateToEntry() + { + switch (Entry.EntryType) + { + case EntryType.Layout: + await _router.Navigate($"workshop/layouts/{Entry.Id}"); + break; + case EntryType.Profile: + await _router.Navigate($"workshop/profiles/{Entry.Id}"); + break; + case EntryType.Plugin: + break; + default: + throw new ArgumentOutOfRangeException(); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListEntryView.axaml b/src/Artemis.UI/Screens/Workshop/Profile/ProfileListEntryView.axaml deleted file mode 100644 index 13395d7b7..000000000 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListEntryView.axaml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - Downloads - Last updated - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListEntryView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Profile/ProfileListEntryView.axaml.cs deleted file mode 100644 index 288da66ae..000000000 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListEntryView.axaml.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; - -namespace Artemis.UI.Screens.Workshop.Profile; - -public partial class ProfileListEntryView : UserControl -{ - public ProfileListEntryView() - { - InitializeComponent(); - } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListEntryViewModel.cs b/src/Artemis.UI/Screens/Workshop/Profile/ProfileListEntryViewModel.cs deleted file mode 100644 index 67786214d..000000000 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListEntryViewModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Artemis.UI.Shared; -using Artemis.WebClient.Workshop; - -namespace Artemis.UI.Screens.Workshop.Profile; - -public class ProfileListEntryViewModel : ViewModelBase -{ - public ProfileListEntryViewModel(IGetEntries_Entries_Nodes entry) - { - Entry = entry; - } - - public IGetEntries_Entries_Nodes Entry { get; } -} \ 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 index 76f1fa762..ecbdb28ff 100644 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileListViewModel.cs @@ -1,15 +1,18 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; using Artemis.UI.Screens.Workshop.Categories; +using Artemis.UI.Screens.Workshop.Entries; using Artemis.UI.Screens.Workshop.Parameters; using Artemis.UI.Shared; using Artemis.UI.Shared.Routing; using Artemis.WebClient.Workshop; using DynamicData; +using DynamicData.Alias; using ReactiveUI; using StrawberryShake; @@ -21,21 +24,31 @@ public class ProfileListViewModel : RoutableScreen(); _entries.Connect() - .Transform(e => new ProfileListEntryViewModel(e)) - .Bind(out ReadOnlyObservableCollection observableEntries) + .Transform(e => new EntryListViewModel(e, router)) + .Bind(out ReadOnlyObservableCollection observableEntries) .Subscribe(); + + this.WhenActivated(d => + { + CategoriesViewModel._categories.Connect() + .AutoRefresh(c => c.IsSelected) + .Filter(e => e.IsSelected) + .Select(e => e.Id) + .Subscribe(_ => ReactiveCommand.CreateFromTask(GetEntries).Execute().Subscribe()) + .DisposeWith(d); + }); Entries = observableEntries; } public CategoriesViewModel CategoriesViewModel { get; } - public ReadOnlyObservableCollection Entries { get; set; } + public ReadOnlyObservableCollection Entries { get; set; } public int Page { @@ -64,7 +77,13 @@ public class ProfileListViewModel : RoutableScreen categories = CategoriesViewModel.Categories.Where(c => c.IsSelected).Select(c => (int?) c.Id).ToList(); + if (categories.Any()) + filter.Categories = new ListFilterInputTypeOfCategoryFilterInput {All = new CategoryFilterInput {Id = new IntOperationFilterInput {In = categories}}}; + + return filter; } public EntryType? EntryType => null; diff --git a/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml b/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml index c4b5f5063..e46ea18ac 100644 --- a/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Search/SearchView.axaml @@ -16,13 +16,13 @@ MaxWidth="500" Watermark="Search" Margin="0 5" - ValueMemberBinding="{CompiledBinding Name, DataType=workshop:ISearchEntries_Entries_Nodes}" + ValueMemberBinding="{CompiledBinding Name, DataType=workshop:ISearchEntries_SearchEntries}" AsyncPopulator="{CompiledBinding SearchAsync}" SelectedItem="{CompiledBinding SelectedEntry}" FilterMode="None" windowing:AppWindow.AllowInteractionInTitleBar="True"> - + diff --git a/src/Artemis.UI/Screens/Workshop/Search/SearchViewModel.cs b/src/Artemis.UI/Screens/Workshop/Search/SearchViewModel.cs index 9d66eb0ba..b3370ee8b 100644 --- a/src/Artemis.UI/Screens/Workshop/Search/SearchViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Search/SearchViewModel.cs @@ -18,7 +18,7 @@ public class SearchViewModel : ViewModelBase private readonly IRouter _router; private readonly IWorkshopClient _workshopClient; private EntryType? _entryType; - private ISearchEntries_Entries_Nodes? _selectedEntry; + private ISearchEntries_SearchEntries? _selectedEntry; public SearchViewModel(IWorkshopClient workshopClient, IRouter router, CurrentUserViewModel currentUserViewModel) { @@ -32,7 +32,7 @@ public class SearchViewModel : ViewModelBase public Func>> SearchAsync { get; } - public ISearchEntries_Entries_Nodes? SelectedEntry + public ISearchEntries_SearchEntries? SelectedEntry { get => _selectedEntry; set => RaiseAndSetIfChanged(ref _selectedEntry, value); @@ -44,7 +44,7 @@ public class SearchViewModel : ViewModelBase set => RaiseAndSetIfChanged(ref _entryType, value); } - private void NavigateToEntry(ISearchEntries_Entries_Nodes entry) + private void NavigateToEntry(ISearchEntries_SearchEntries entry) { string? url = null; if (entry.EntryType == WebClient.Workshop.EntryType.Profile) @@ -60,21 +60,8 @@ public class SearchViewModel : ViewModelBase { if (string.IsNullOrWhiteSpace(input)) return new List(); - - EntryFilterInput filter; - if (EntryType != null) - filter = new EntryFilterInput - { - And = new[] - { - new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = EntryType}}, - new EntryFilterInput {Name = new StringOperationFilterInput {Contains = input}} - } - }; - else - filter = new EntryFilterInput {Name = new StringOperationFilterInput {Contains = input}}; - - IOperationResult results = await _workshopClient.SearchEntries.ExecuteAsync(filter, cancellationToken); - return results.Data?.Entries?.Nodes?.Cast() ?? new List(); + + IOperationResult results = await _workshopClient.SearchEntries.ExecuteAsync(input, EntryType, cancellationToken); + return results.Data?.SearchEntries.Cast() ?? new List(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Styles/TreeView.axaml b/src/Artemis.UI/Styles/TreeView.axaml index 754755713..0041e20fb 100644 --- a/src/Artemis.UI/Styles/TreeView.axaml +++ b/src/Artemis.UI/Styles/TreeView.axaml @@ -1,7 +1,6 @@  @@ -77,7 +76,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" MinHeight="{DynamicResource NavigationViewItemOnLeftMinHeight}" - CornerRadius="{DynamicResource OverlayCornerRadius}" + CornerRadius="{DynamicResource ControlCornerRadius}" TemplatedControl.IsTemplateFocusTarget="True" Margin="2"> diff --git a/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql b/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql index dae8242c6..3ae3fc34b 100644 --- a/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql +++ b/src/Artemis.WebClient.Workshop/Queries/GetEntries.graphql @@ -1,10 +1,13 @@ query GetEntries($filter: EntryFilterInput) { entries(where: $filter) { nodes { + id author name summary entryType + downloads + createdAt categories { name icon diff --git a/src/Artemis.WebClient.Workshop/Queries/SearchEntries.graphql b/src/Artemis.WebClient.Workshop/Queries/SearchEntries.graphql index b2a03cf3f..620aeb70d 100644 --- a/src/Artemis.WebClient.Workshop/Queries/SearchEntries.graphql +++ b/src/Artemis.WebClient.Workshop/Queries/SearchEntries.graphql @@ -1,18 +1,13 @@ -query SearchEntries($filter: EntryFilterInput) { - entries( - first: 10 - where: $filter - ) { - nodes { +query SearchEntries($input: String! $type: EntryType) { + searchEntries(input: $input type: $type) { + id + name + summary + entryType + categories { id name - summary - entryType - categories { - id - name - icon - } + icon } } }