mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Workshop list progress
This commit is contained in:
parent
2a3fd30313
commit
07d4539add
@ -22,6 +22,6 @@
|
|||||||
|
|
||||||
<Style Selector="ListBox.sidebar-listbox ListBoxItem /template/ ContentPresenter#PART_ContentPresenter">
|
<Style Selector="ListBox.sidebar-listbox ListBoxItem /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
<Setter Property="MinHeight" Value="{DynamicResource NavigationViewItemOnLeftMinHeight}" />
|
<Setter Property="MinHeight" Value="{DynamicResource NavigationViewItemOnLeftMinHeight}" />
|
||||||
<Setter Property="CornerRadius" Value="{DynamicResource OverlayCornerRadius}" />
|
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
|
||||||
</Style>
|
</Style>
|
||||||
</Styles>
|
</Styles>
|
||||||
@ -10,6 +10,15 @@
|
|||||||
<TextBlock Classes="h5">This is heading 5</TextBlock>
|
<TextBlock Classes="h5">This is heading 5</TextBlock>
|
||||||
<TextBlock Classes="h6">This is heading 6</TextBlock>
|
<TextBlock Classes="h6">This is heading 6</TextBlock>
|
||||||
<TextBlock Classes="subtitle">This is a subtitle</TextBlock>
|
<TextBlock Classes="subtitle">This is a subtitle</TextBlock>
|
||||||
|
<TextBlock>
|
||||||
|
<Run Classes="h1">This is heading 1</Run>
|
||||||
|
<Run Classes="h2">This is heading 2</Run>
|
||||||
|
<Run Classes="h3">This is heading 3</Run>
|
||||||
|
<Run Classes="h4">This is heading 4</Run>
|
||||||
|
<Run Classes="h5">This is heading 5</Run>
|
||||||
|
<Run Classes="h6">This is heading 6</Run>
|
||||||
|
<Run Classes="subtitle">This is a subtitle</Run>
|
||||||
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Design.PreviewWith>
|
</Design.PreviewWith>
|
||||||
@ -50,4 +59,26 @@
|
|||||||
<Setter Property="FontWeight" Value="Medium" />
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
<Setter Property="Margin" Value="0 25 0 5" />
|
<Setter Property="Margin" Value="0 25 0 5" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Run.h1">
|
||||||
|
<Setter Property="FontSize" Value="64" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Run.h2">
|
||||||
|
<Setter Property="FontSize" Value="48" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Run.h3">
|
||||||
|
<Setter Property="FontSize" Value="32" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Run.h4">
|
||||||
|
<Setter Property="FontSize" Value="24" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Run.h5">
|
||||||
|
<Setter Property="FontSize" Value="18" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Run.h6">
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Run.subtitle">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorTertiaryBrush}" />
|
||||||
|
</Style>
|
||||||
</Styles>
|
</Styles>
|
||||||
|
|||||||
@ -55,4 +55,11 @@
|
|||||||
<UpToDateCheckInput Remove="Screens\Workshop\Categories\Profile\ProfileDetailsView.axaml" />
|
<UpToDateCheckInput Remove="Screens\Workshop\Categories\Profile\ProfileDetailsView.axaml" />
|
||||||
<UpToDateCheckInput Remove="Screens\Workshop\Categories\Profile\ProfileListView.axaml" />
|
<UpToDateCheckInput Remove="Screens\Workshop\Categories\Profile\ProfileListView.axaml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Screens\Workshop\Enries\EntryListView.axaml.cs">
|
||||||
|
<DependentUpon>ProfileListEntryView.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
|
using DynamicData;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using StrawberryShake;
|
using StrawberryShake;
|
||||||
@ -16,21 +17,21 @@ public class CategoriesViewModel : ActivatableViewModelBase
|
|||||||
{
|
{
|
||||||
private readonly IWorkshopClient _client;
|
private readonly IWorkshopClient _client;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private IReadOnlyList<CategoryViewModel> _categories;
|
public readonly SourceList<CategoryViewModel> _categories;
|
||||||
|
|
||||||
public CategoriesViewModel(ILogger logger, IWorkshopClient client)
|
public CategoriesViewModel(ILogger logger, IWorkshopClient client)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_client = client;
|
_client = client;
|
||||||
|
_categories = new SourceList<CategoryViewModel>();
|
||||||
|
_categories.Connect().Bind(out ReadOnlyObservableCollection<CategoryViewModel> categoryViewModels).Subscribe();
|
||||||
|
|
||||||
|
Categories = categoryViewModels;
|
||||||
this.WhenActivated(d => ReactiveCommand.CreateFromTask(GetCategories).Execute().Subscribe().DisposeWith(d));
|
this.WhenActivated(d => ReactiveCommand.CreateFromTask(GetCategories).Execute().Subscribe().DisposeWith(d));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<CategoryViewModel> Categories
|
public ReadOnlyObservableCollection<CategoryViewModel> Categories { get; }
|
||||||
{
|
|
||||||
get => _categories;
|
|
||||||
set => RaiseAndSetIfChanged(ref _categories, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task GetCategories(CancellationToken cancellationToken)
|
private async Task GetCategories(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@ -40,7 +41,12 @@ public class CategoriesViewModel : ActivatableViewModelBase
|
|||||||
if (result.IsErrorResult())
|
if (result.IsErrorResult())
|
||||||
_logger.Warning("Failed to retrieve categories {Error}", result.Errors);
|
_logger.Warning("Failed to retrieve categories {Error}", result.Errors);
|
||||||
|
|
||||||
Categories = result.Data?.Categories.Select(c => new CategoryViewModel(c)).ToList() ?? new List<CategoryViewModel>();
|
_categories.Edit(l =>
|
||||||
|
{
|
||||||
|
l.Clear();
|
||||||
|
if (result.Data?.Categories != null)
|
||||||
|
l.AddRange(result.Data.Categories.Select(c => new CategoryViewModel(c)));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -9,9 +9,9 @@
|
|||||||
x:Class="Artemis.UI.Screens.Workshop.CurrentUser.CurrentUserView"
|
x:Class="Artemis.UI.Screens.Workshop.CurrentUser.CurrentUserView"
|
||||||
x:DataType="currentUser:CurrentUserViewModel">
|
x:DataType="currentUser:CurrentUserViewModel">
|
||||||
|
|
||||||
<Panel>
|
<Panel IsVisible="{CompiledBinding !Loading}">
|
||||||
<!-- Signed out -->
|
<!-- Signed out -->
|
||||||
<Ellipse Height="28" Width="28" IsVisible="{CompiledBinding !IsLoggedIn}">
|
<Ellipse Height="28" Width="28" IsVisible="{CompiledBinding Name, Converter={x:Static StringConverters.IsNullOrEmpty}}">
|
||||||
<Ellipse.ContextFlyout>
|
<Ellipse.ContextFlyout>
|
||||||
<MenuFlyout>
|
<MenuFlyout>
|
||||||
<MenuItem Header="Login" Command="{CompiledBinding Login}">
|
<MenuItem Header="Login" Command="{CompiledBinding Login}">
|
||||||
@ -27,7 +27,7 @@
|
|||||||
</Ellipse>
|
</Ellipse>
|
||||||
|
|
||||||
<!-- Signed in -->
|
<!-- Signed in -->
|
||||||
<Ellipse Height="28" Width="28" IsVisible="{CompiledBinding IsLoggedIn}" Name="UserMenu">
|
<Ellipse Height="28" Width="28" IsVisible="{CompiledBinding Name, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" Name="UserMenu">
|
||||||
<Ellipse.ContextFlyout>
|
<Ellipse.ContextFlyout>
|
||||||
<Flyout>
|
<Flyout>
|
||||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="*,*,*" MinWidth="300">
|
<Grid ColumnDefinitions="Auto,*" RowDefinitions="*,*,*" MinWidth="300">
|
||||||
|
|||||||
@ -10,38 +10,33 @@ using Artemis.WebClient.Workshop.Services;
|
|||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using Flurl.Http;
|
using Flurl.Http;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.CurrentUser;
|
namespace Artemis.UI.Screens.Workshop.CurrentUser;
|
||||||
|
|
||||||
public class CurrentUserViewModel : ActivatableViewModelBase
|
public class CurrentUserViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
private readonly IAuthenticationService _authenticationService;
|
private readonly IAuthenticationService _authenticationService;
|
||||||
private ObservableAsPropertyHelper<bool>? _isLoggedIn;
|
private bool _loading = true;
|
||||||
|
|
||||||
private string? _userId;
|
|
||||||
private string? _name;
|
|
||||||
private string? _email;
|
|
||||||
private Bitmap? _avatar;
|
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;
|
_authenticationService = authenticationService;
|
||||||
Login = ReactiveCommand.CreateFromTask(ExecuteLogin);
|
Login = ReactiveCommand.CreateFromTask(ExecuteLogin);
|
||||||
|
|
||||||
this.WhenActivated(d => _isLoggedIn = _authenticationService.WhenAnyValue(s => s.IsLoggedIn).ToProperty(this, vm => vm.IsLoggedIn).DisposeWith(d));
|
this.WhenActivated(d => ReactiveCommand.CreateFromTask(ExecuteAutoLogin).Execute().Subscribe().DisposeWith(d));
|
||||||
this.WhenActivated(d =>
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await _authenticationService.AutoLogin();
|
|
||||||
await LoadCurrentUser();
|
|
||||||
}).DisposeWith(d);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Logout()
|
public bool Loading
|
||||||
{
|
{
|
||||||
_authenticationService.Logout();
|
get => _loading;
|
||||||
|
set => RaiseAndSetIfChanged(ref _loading, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? UserId
|
public string? UserId
|
||||||
@ -69,18 +64,38 @@ public class CurrentUserViewModel : ActivatableViewModelBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> Login { get; }
|
public ReactiveCommand<Unit, Unit> Login { get; }
|
||||||
public bool IsLoggedIn => _isLoggedIn?.Value ?? false;
|
|
||||||
|
public void Logout()
|
||||||
|
{
|
||||||
|
_authenticationService.Logout();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ExecuteLogin(CancellationToken cancellationToken)
|
private async Task ExecuteLogin(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _authenticationService.Login();
|
await _authenticationService.Login();
|
||||||
await LoadCurrentUser();
|
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()
|
private async Task LoadCurrentUser()
|
||||||
{
|
{
|
||||||
if (!IsLoggedIn)
|
if (!_authenticationService.IsLoggedIn)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
UserId = _authenticationService.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
|
UserId = _authenticationService.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
|
||||||
|
|||||||
61
src/Artemis.UI/Screens/Workshop/Entries/EntryListView.axaml
Normal file
61
src/Artemis.UI/Screens/Workshop/Entries/EntryListView.axaml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
|
xmlns:entries="clr-namespace:Artemis.UI.Screens.Workshop.Profile"
|
||||||
|
xmlns:entries1="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="120"
|
||||||
|
x:Class="Artemis.UI.Screens.Workshop.Entries.EntryListView"
|
||||||
|
x:DataType="entries1:EntryListViewModel">
|
||||||
|
<Border Classes="card" MinHeight="120">
|
||||||
|
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||||
|
<Border Grid.Column="0"
|
||||||
|
Cursor="Hand"
|
||||||
|
CornerRadius="12"
|
||||||
|
Background="{StaticResource ControlStrokeColorOnAccentDefault}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0 0 10 0"
|
||||||
|
Width="90"
|
||||||
|
Height="90"
|
||||||
|
PointerReleased="InputElement_OnPointerReleased">
|
||||||
|
<avalonia:MaterialIcon Kind="HandOkay" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Width="80" Height="80"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
|
||||||
|
<Grid Grid.Column="1" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto">
|
||||||
|
<TextBlock Grid.Row="0" Margin="0 0 0 5" Cursor="Hand" PointerReleased="InputElement_OnPointerReleased">
|
||||||
|
<Run Classes="h4" Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
|
||||||
|
<Run Classes="subtitle">by</Run>
|
||||||
|
<Run Classes="subtitle" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Grid.Row="1" FontSize="15" Classes="subtitle" Text="{CompiledBinding Entry.Summary, FallbackValue=Summary}"></TextBlock>
|
||||||
|
|
||||||
|
<ItemsControl Grid.Row="2" ItemsSource="{CompiledBinding Entry.Categories}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8"></StackPanel>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Margin="0 0 3 0"></avalonia:MaterialIcon>
|
||||||
|
<TextBlock Text="{CompiledBinding Name}" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
<StackPanel Grid.Column="2">
|
||||||
|
<TextBlock TextAlignment="Right" Text="{CompiledBinding Entry.CreatedAt, StringFormat={}{0:g}, FallbackValue=01-01-1337}" />
|
||||||
|
<TextBlock TextAlignment="Right">
|
||||||
|
<avalonia:MaterialIcon Kind="Downloads" />
|
||||||
|
<Run Classes="h5" Text="{CompiledBinding Entry.Downloads, FallbackValue=0}" />
|
||||||
|
<Run>downloads</Run>
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
@ -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<EntryListViewModel>
|
||||||
|
{
|
||||||
|
public EntryListView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
{
|
||||||
|
await ViewModel.NavigateToEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,39 +0,0 @@
|
|||||||
<UserControl xmlns="https://github.com/avaloniaui"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
|
||||||
xmlns:profile="clr-namespace:Artemis.UI.Screens.Workshop.Profile"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="120"
|
|
||||||
x:Class="Artemis.UI.Screens.Workshop.Profile.ProfileListEntryView"
|
|
||||||
x:DataType="profile:ProfileListEntryViewModel">
|
|
||||||
<Border Classes="card">
|
|
||||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
|
||||||
<avalonia:MaterialIcon Kind="Abacus" Width="80" Height="80" Margin="0 0 10 0" Grid.Column="0" VerticalAlignment="Center" />
|
|
||||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
|
||||||
<TextBlock Classes="h4 no-margin" Text="{CompiledBinding Entry.Name, FallbackValue=Title}"></TextBlock>
|
|
||||||
<TextBlock Classes="subtitle" Text="{CompiledBinding Entry.Summary, FallbackValue=Summary}"></TextBlock>
|
|
||||||
|
|
||||||
<ItemsControl ItemsSource="{CompiledBinding Entry.Categories}">
|
|
||||||
<ItemsControl.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8"></StackPanel>
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ItemsControl.ItemsPanel>
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Margin="0 0 3 0"></avalonia:MaterialIcon>
|
|
||||||
<TextBlock Text="{CompiledBinding Name}"></TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
</ItemsControl>
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Grid.Column="2">
|
|
||||||
<TextBlock>Downloads</TextBlock>
|
|
||||||
<TextBlock>Last updated</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</UserControl>
|
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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; }
|
|
||||||
}
|
|
||||||
@ -1,15 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.UI.Screens.Workshop.Categories;
|
using Artemis.UI.Screens.Workshop.Categories;
|
||||||
|
using Artemis.UI.Screens.Workshop.Entries;
|
||||||
using Artemis.UI.Screens.Workshop.Parameters;
|
using Artemis.UI.Screens.Workshop.Parameters;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
|
using DynamicData.Alias;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using StrawberryShake;
|
using StrawberryShake;
|
||||||
|
|
||||||
@ -21,21 +24,31 @@ public class ProfileListViewModel : RoutableScreen<ActivatableViewModelBase, Wor
|
|||||||
private readonly IWorkshopClient _workshopClient;
|
private readonly IWorkshopClient _workshopClient;
|
||||||
private int _page;
|
private int _page;
|
||||||
|
|
||||||
public ProfileListViewModel(IWorkshopClient workshopClient, CategoriesViewModel categoriesViewModel)
|
public ProfileListViewModel(IWorkshopClient workshopClient, IRouter router, CategoriesViewModel categoriesViewModel)
|
||||||
{
|
{
|
||||||
_workshopClient = workshopClient;
|
_workshopClient = workshopClient;
|
||||||
CategoriesViewModel = categoriesViewModel;
|
CategoriesViewModel = categoriesViewModel;
|
||||||
|
|
||||||
_entries = new SourceList<IGetEntries_Entries_Nodes>();
|
_entries = new SourceList<IGetEntries_Entries_Nodes>();
|
||||||
_entries.Connect()
|
_entries.Connect()
|
||||||
.Transform(e => new ProfileListEntryViewModel(e))
|
.Transform(e => new EntryListViewModel(e, router))
|
||||||
.Bind(out ReadOnlyObservableCollection<ProfileListEntryViewModel> observableEntries)
|
.Bind(out ReadOnlyObservableCollection<EntryListViewModel> observableEntries)
|
||||||
.Subscribe();
|
.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;
|
Entries = observableEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CategoriesViewModel CategoriesViewModel { get; }
|
public CategoriesViewModel CategoriesViewModel { get; }
|
||||||
public ReadOnlyObservableCollection<ProfileListEntryViewModel> Entries { get; set; }
|
public ReadOnlyObservableCollection<EntryListViewModel> Entries { get; set; }
|
||||||
|
|
||||||
public int Page
|
public int Page
|
||||||
{
|
{
|
||||||
@ -64,7 +77,13 @@ public class ProfileListViewModel : RoutableScreen<ActivatableViewModelBase, Wor
|
|||||||
|
|
||||||
private EntryFilterInput CreateFilter()
|
private EntryFilterInput CreateFilter()
|
||||||
{
|
{
|
||||||
return new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = WebClient.Workshop.EntryType.Profile}};
|
EntryFilterInput filter = new() {EntryType = new EntryTypeOperationFilterInput {Eq = WebClient.Workshop.EntryType.Profile}};
|
||||||
|
|
||||||
|
List<int?> 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;
|
public EntryType? EntryType => null;
|
||||||
|
|||||||
@ -16,13 +16,13 @@
|
|||||||
MaxWidth="500"
|
MaxWidth="500"
|
||||||
Watermark="Search"
|
Watermark="Search"
|
||||||
Margin="0 5"
|
Margin="0 5"
|
||||||
ValueMemberBinding="{CompiledBinding Name, DataType=workshop:ISearchEntries_Entries_Nodes}"
|
ValueMemberBinding="{CompiledBinding Name, DataType=workshop:ISearchEntries_SearchEntries}"
|
||||||
AsyncPopulator="{CompiledBinding SearchAsync}"
|
AsyncPopulator="{CompiledBinding SearchAsync}"
|
||||||
SelectedItem="{CompiledBinding SelectedEntry}"
|
SelectedItem="{CompiledBinding SelectedEntry}"
|
||||||
FilterMode="None"
|
FilterMode="None"
|
||||||
windowing:AppWindow.AllowInteractionInTitleBar="True">
|
windowing:AppWindow.AllowInteractionInTitleBar="True">
|
||||||
<AutoCompleteBox.ItemTemplate>
|
<AutoCompleteBox.ItemTemplate>
|
||||||
<DataTemplate x:DataType="workshop:ISearchEntries_Entries_Nodes">
|
<DataTemplate x:DataType="workshop:ISearchEntries_SearchEntries">
|
||||||
<Panel>
|
<Panel>
|
||||||
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Center">
|
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||||
<TextBlock Text="{CompiledBinding Name}" />
|
<TextBlock Text="{CompiledBinding Name}" />
|
||||||
|
|||||||
@ -18,7 +18,7 @@ public class SearchViewModel : ViewModelBase
|
|||||||
private readonly IRouter _router;
|
private readonly IRouter _router;
|
||||||
private readonly IWorkshopClient _workshopClient;
|
private readonly IWorkshopClient _workshopClient;
|
||||||
private EntryType? _entryType;
|
private EntryType? _entryType;
|
||||||
private ISearchEntries_Entries_Nodes? _selectedEntry;
|
private ISearchEntries_SearchEntries? _selectedEntry;
|
||||||
|
|
||||||
public SearchViewModel(IWorkshopClient workshopClient, IRouter router, CurrentUserViewModel currentUserViewModel)
|
public SearchViewModel(IWorkshopClient workshopClient, IRouter router, CurrentUserViewModel currentUserViewModel)
|
||||||
{
|
{
|
||||||
@ -32,7 +32,7 @@ public class SearchViewModel : ViewModelBase
|
|||||||
|
|
||||||
public Func<string?, CancellationToken, Task<IEnumerable<object>>> SearchAsync { get; }
|
public Func<string?, CancellationToken, Task<IEnumerable<object>>> SearchAsync { get; }
|
||||||
|
|
||||||
public ISearchEntries_Entries_Nodes? SelectedEntry
|
public ISearchEntries_SearchEntries? SelectedEntry
|
||||||
{
|
{
|
||||||
get => _selectedEntry;
|
get => _selectedEntry;
|
||||||
set => RaiseAndSetIfChanged(ref _selectedEntry, value);
|
set => RaiseAndSetIfChanged(ref _selectedEntry, value);
|
||||||
@ -44,7 +44,7 @@ public class SearchViewModel : ViewModelBase
|
|||||||
set => RaiseAndSetIfChanged(ref _entryType, value);
|
set => RaiseAndSetIfChanged(ref _entryType, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NavigateToEntry(ISearchEntries_Entries_Nodes entry)
|
private void NavigateToEntry(ISearchEntries_SearchEntries entry)
|
||||||
{
|
{
|
||||||
string? url = null;
|
string? url = null;
|
||||||
if (entry.EntryType == WebClient.Workshop.EntryType.Profile)
|
if (entry.EntryType == WebClient.Workshop.EntryType.Profile)
|
||||||
@ -60,21 +60,8 @@ public class SearchViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(input))
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
return new List<object>();
|
return new List<object>();
|
||||||
|
|
||||||
EntryFilterInput filter;
|
IOperationResult<ISearchEntriesResult> results = await _workshopClient.SearchEntries.ExecuteAsync(input, EntryType, cancellationToken);
|
||||||
if (EntryType != null)
|
return results.Data?.SearchEntries.Cast<object>() ?? new List<object>();
|
||||||
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<ISearchEntriesResult> results = await _workshopClient.SearchEntries.ExecuteAsync(filter, cancellationToken);
|
|
||||||
return results.Data?.Entries?.Nodes?.Cast<object>() ?? new List<object>();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,7 +1,6 @@
|
|||||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:converters="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls"
|
xmlns:converters="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls"
|
||||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
x:CompileBindings="True">
|
x:CompileBindings="True">
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
@ -77,7 +76,7 @@
|
|||||||
BorderBrush="{TemplateBinding BorderBrush}"
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
BorderThickness="{TemplateBinding BorderThickness}"
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
MinHeight="{DynamicResource NavigationViewItemOnLeftMinHeight}"
|
MinHeight="{DynamicResource NavigationViewItemOnLeftMinHeight}"
|
||||||
CornerRadius="{DynamicResource OverlayCornerRadius}"
|
CornerRadius="{DynamicResource ControlCornerRadius}"
|
||||||
TemplatedControl.IsTemplateFocusTarget="True"
|
TemplatedControl.IsTemplateFocusTarget="True"
|
||||||
Margin="2">
|
Margin="2">
|
||||||
<Panel>
|
<Panel>
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
query GetEntries($filter: EntryFilterInput) {
|
query GetEntries($filter: EntryFilterInput) {
|
||||||
entries(where: $filter) {
|
entries(where: $filter) {
|
||||||
nodes {
|
nodes {
|
||||||
|
id
|
||||||
author
|
author
|
||||||
name
|
name
|
||||||
summary
|
summary
|
||||||
entryType
|
entryType
|
||||||
|
downloads
|
||||||
|
createdAt
|
||||||
categories {
|
categories {
|
||||||
name
|
name
|
||||||
icon
|
icon
|
||||||
|
|||||||
@ -1,18 +1,13 @@
|
|||||||
query SearchEntries($filter: EntryFilterInput) {
|
query SearchEntries($input: String! $type: EntryType) {
|
||||||
entries(
|
searchEntries(input: $input type: $type) {
|
||||||
first: 10
|
id
|
||||||
where: $filter
|
name
|
||||||
) {
|
summary
|
||||||
nodes {
|
entryType
|
||||||
|
categories {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
summary
|
icon
|
||||||
entryType
|
|
||||||
categories {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
icon
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user