mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 11:56:30 +00:00
Entry list - Show icons
Workshop search - Show icons, update design
This commit is contained in:
1 parent
176a28761f
commit
77bed1bf94
13 files changed
+273
-76
No files matched your search
@@ -0,0 +1,60 @@
|
||||
<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:search="clr-namespace:Artemis.UI.Screens.Workshop.Search"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="80"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Search.SearchResultView"
|
||||
x:DataType="search:SearchResultViewModel">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" Margin="0 5">
|
||||
<!-- Icon -->
|
||||
<Border Grid.Column="0"
|
||||
CornerRadius="6"
|
||||
Background="{StaticResource ControlStrokeColorOnAccentDefault}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 10 0"
|
||||
Width="50"
|
||||
Height="50"
|
||||
ClipToBounds="True">
|
||||
<Image Source="{CompiledBinding EntryIcon}"
|
||||
Stretch="UniformToFill"
|
||||
Classes="fade-in"
|
||||
Classes.faded-in="{CompiledBinding EntryIcon, Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||
</Border>
|
||||
|
||||
<!-- Body -->
|
||||
<Grid Grid.Column="1" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Row="0" TextTrimming="CharacterEllipsis">
|
||||
<Run Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
|
||||
<Run Classes="subtitle" FontSize="12">by</Run>
|
||||
<Run Classes="subtitle" FontSize="12" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="1"
|
||||
Classes="subtitle"
|
||||
TextWrapping="Wrap"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
FontSize="13"
|
||||
Text="{CompiledBinding Entry.Summary, FallbackValue=Summary}">
|
||||
</TextBlock>
|
||||
|
||||
<ItemsControl Grid.Row="2" ItemsSource="{CompiledBinding Entry.Categories}" Margin="0 5">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="5"></StackPanel>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Classes="badge">
|
||||
<TextBlock Text="{CompiledBinding Name}"></TextBlock>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Column="2" Classes="badge" VerticalAlignment="Top" Margin="0 5 0 0">
|
||||
<TextBlock Text="{CompiledBinding Entry.EntryType}"></TextBlock>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Search;
|
||||
|
||||
public partial class SearchResultView : ReactiveUserControl<SearchResultViewModel>
|
||||
{
|
||||
public SearchResultView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using Avalonia.Media.Imaging;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Search;
|
||||
|
||||
public class SearchResultViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly IWorkshopService _workshopService;
|
||||
private ObservableAsPropertyHelper<Bitmap?>? _entryIcon;
|
||||
|
||||
public SearchResultViewModel(ISearchEntries_SearchEntries entry, IWorkshopService workshopService)
|
||||
{
|
||||
_workshopService = workshopService;
|
||||
|
||||
Entry = entry;
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
_entryIcon = Observable.FromAsync(c => _workshopService.GetEntryIcon(Entry.Id, c)).ToProperty(this, vm => vm.EntryIcon);
|
||||
_entryIcon.DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
public ISearchEntries_SearchEntries Entry { get; }
|
||||
public Bitmap? EntryIcon => _entryIcon?.Value;
|
||||
}
|
||||
@@ -11,47 +11,31 @@
|
||||
<UserControl.Styles>
|
||||
<StyleInclude Source="SearchViewStyles.axaml" />
|
||||
</UserControl.Styles>
|
||||
<Panel>
|
||||
<Panel Margin="0 5">
|
||||
<AutoCompleteBox Name="SearchBox"
|
||||
MaxWidth="500"
|
||||
Watermark="Search"
|
||||
Margin="0 5"
|
||||
ValueMemberBinding="{CompiledBinding Name, DataType=workshop:ISearchEntries_SearchEntries}"
|
||||
MinimumPopulateDelay="0:0:0.8"
|
||||
ValueMemberBinding="{CompiledBinding Entry.Name, DataType=search:SearchResultViewModel}"
|
||||
AsyncPopulator="{CompiledBinding SearchAsync}"
|
||||
SelectedItem="{CompiledBinding SelectedEntry}"
|
||||
FilterMode="None"
|
||||
windowing:AppWindow.AllowInteractionInTitleBar="True">
|
||||
<AutoCompleteBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="workshop:ISearchEntries_SearchEntries">
|
||||
<Panel>
|
||||
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||
<TextBlock Text="{CompiledBinding Name}" />
|
||||
<TextBlock Text="{CompiledBinding Summary}" Foreground="{DynamicResource TextFillColorSecondary}" />
|
||||
|
||||
<ItemsControl ItemsSource="{CompiledBinding Categories}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="5"></StackPanel>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Classes="category">
|
||||
<TextBlock Text="{CompiledBinding Name}"></TextBlock>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{Binding}" Margin="-5 0 0 0"></ContentControl>
|
||||
</DataTemplate>
|
||||
</AutoCompleteBox.ItemTemplate>
|
||||
</AutoCompleteBox>
|
||||
<ContentControl HorizontalAlignment="Right"
|
||||
<ContentControl HorizontalAlignment="Right"
|
||||
Width="28"
|
||||
Height="28"
|
||||
Margin="0 0 50 0"
|
||||
Content="{CompiledBinding CurrentUserViewModel}"
|
||||
windowing:AppWindow.AllowInteractionInTitleBar="True"/>
|
||||
windowing:AppWindow.AllowInteractionInTitleBar="True" />
|
||||
|
||||
<Border VerticalAlignment="Top" CornerRadius="4 4 0 0" ClipToBounds="True" MaxWidth="500">
|
||||
<ProgressBar IsIndeterminate="True" VerticalAlignment="Top" IsVisible="{CompiledBinding IsLoading}"></ProgressBar>
|
||||
</Border>
|
||||
</Panel>
|
||||
</UserControl>
|
||||
@@ -7,32 +7,40 @@ using Artemis.UI.Screens.Workshop.CurrentUser;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using ReactiveUI;
|
||||
using Serilog;
|
||||
using StrawberryShake;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Search;
|
||||
|
||||
public class SearchViewModel : ViewModelBase
|
||||
{
|
||||
public CurrentUserViewModel CurrentUserViewModel { get; }
|
||||
private readonly ILogger _logger;
|
||||
private readonly IRouter _router;
|
||||
private readonly IWorkshopClient _workshopClient;
|
||||
private readonly IWorkshopService _workshopService;
|
||||
private EntryType? _entryType;
|
||||
private ISearchEntries_SearchEntries? _selectedEntry;
|
||||
private bool _isLoading;
|
||||
private SearchResultViewModel? _selectedEntry;
|
||||
|
||||
public SearchViewModel(IWorkshopClient workshopClient, IRouter router, CurrentUserViewModel currentUserViewModel)
|
||||
public SearchViewModel(ILogger logger, IWorkshopClient workshopClient, IWorkshopService workshopService, IRouter router, CurrentUserViewModel currentUserViewModel)
|
||||
{
|
||||
CurrentUserViewModel = currentUserViewModel;
|
||||
_logger = logger;
|
||||
_workshopClient = workshopClient;
|
||||
_workshopService = workshopService;
|
||||
_router = router;
|
||||
CurrentUserViewModel = currentUserViewModel;
|
||||
SearchAsync = ExecuteSearchAsync;
|
||||
|
||||
this.WhenAnyValue(vm => vm.SelectedEntry).WhereNotNull().Subscribe(NavigateToEntry);
|
||||
}
|
||||
|
||||
public CurrentUserViewModel CurrentUserViewModel { get; }
|
||||
|
||||
public Func<string?, CancellationToken, Task<IEnumerable<object>>> SearchAsync { get; }
|
||||
|
||||
public ISearchEntries_SearchEntries? SelectedEntry
|
||||
public SearchResultViewModel? SelectedEntry
|
||||
{
|
||||
get => _selectedEntry;
|
||||
set => RaiseAndSetIfChanged(ref _selectedEntry, value);
|
||||
@@ -44,13 +52,19 @@ public class SearchViewModel : ViewModelBase
|
||||
set => RaiseAndSetIfChanged(ref _entryType, value);
|
||||
}
|
||||
|
||||
private void NavigateToEntry(ISearchEntries_SearchEntries entry)
|
||||
public bool IsLoading
|
||||
{
|
||||
get => _isLoading;
|
||||
set => RaiseAndSetIfChanged(ref _isLoading, value);
|
||||
}
|
||||
|
||||
private void NavigateToEntry(SearchResultViewModel searchResult)
|
||||
{
|
||||
string? url = null;
|
||||
if (entry.EntryType == WebClient.Workshop.EntryType.Profile)
|
||||
url = $"workshop/profiles/{entry.Id}";
|
||||
if (entry.EntryType == WebClient.Workshop.EntryType.Layout)
|
||||
url = $"workshop/layouts/{entry.Id}";
|
||||
if (searchResult.Entry.EntryType == WebClient.Workshop.EntryType.Profile)
|
||||
url = $"workshop/profiles/{searchResult.Entry.Id}";
|
||||
if (searchResult.Entry.EntryType == WebClient.Workshop.EntryType.Layout)
|
||||
url = $"workshop/layouts/{searchResult.Entry.Id}";
|
||||
|
||||
if (url != null)
|
||||
Task.Run(() => _router.Navigate(url));
|
||||
@@ -58,10 +72,25 @@ public class SearchViewModel : ViewModelBase
|
||||
|
||||
private async Task<IEnumerable<object>> ExecuteSearchAsync(string? input, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
return new List<object>();
|
||||
|
||||
IOperationResult<ISearchEntriesResult> results = await _workshopClient.SearchEntries.ExecuteAsync(input, EntryType, cancellationToken);
|
||||
return results.Data?.SearchEntries.Cast<object>() ?? new List<object>();
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input) || input.Length < 2)
|
||||
return new List<object>();
|
||||
|
||||
IsLoading = true;
|
||||
IOperationResult<ISearchEntriesResult> results = await _workshopClient.SearchEntries.ExecuteAsync(input, EntryType, cancellationToken);
|
||||
return results.Data?.SearchEntries.Select(e => new SearchResultViewModel(e, _workshopService) as object) ?? new List<object>();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is not TaskCanceledException)
|
||||
_logger.Error(e, "Failed to execute search");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
|
||||
return new List<object>();
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
</Design.PreviewWith>
|
||||
|
||||
<!-- Add Styles Here -->
|
||||
<Style Selector="Border.category">
|
||||
<Style Selector="Border.badge">
|
||||
<Setter Property="Background" Value="{DynamicResource ControlSolidFillColorDefaultBrush}" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
<Setter Property="Padding" Value="6 1"></Setter>
|
||||
|
||||
Reference in new issue
Block a user