mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-12 13:28:33 +00:00
Show multiple releases and improve workshop routing
This commit is contained in:
parent
da3d47d7b8
commit
9c04932afa
@ -44,5 +44,22 @@
|
||||
<DependentUpon>DeviceSelectionDialogView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Screens\Workshop\Layout\LayoutListView.axaml.cs">
|
||||
<DependentUpon>LayoutListView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Screens\Workshop\Plugins\PluginListView.axaml.cs">
|
||||
<DependentUpon>LayoutListView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Screens\Workshop\Profile\ProfileListView.axaml.cs">
|
||||
<DependentUpon>LayoutListView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Remove="Screens\Workshop\Entries\Tabs\PluginListView.axaml" />
|
||||
<UpToDateCheckInput Remove="Screens\Workshop\Entries\Tabs\ProfileListView.axaml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -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;
|
||||
|
||||
@ -19,7 +19,7 @@ public partial class RootView : ReactiveUserControl<RootViewModel>
|
||||
{
|
||||
try
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => RootFrame.NavigateFromObject(viewModel));
|
||||
RootFrame.NavigateFromObject(viewModel);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
@ -18,7 +18,7 @@ public partial class SettingsView : ReactiveUserControl<SettingsViewModel>
|
||||
|
||||
private void Navigate(ViewModelBase viewModel)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => TabFrame.NavigateFromObject(viewModel));
|
||||
TabFrame.NavigateFromObject(viewModel);
|
||||
}
|
||||
|
||||
private void NavigationView_OnBackRequested(object? sender, NavigationViewBackRequestedEventArgs e)
|
||||
|
||||
@ -17,17 +17,13 @@ public partial class ReleasesTabView : ReactiveUserControl<ReleasesTabViewModel>
|
||||
|
||||
private void Navigate(ViewModelBase viewModel)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
ReleaseFrame.NavigateFromObject(viewModel);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
});
|
||||
ReleaseFrame.NavigateFromObject(viewModel);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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 @@
|
||||
<sharedConverters:BytesToStringConverter x:Key="BytesToStringConverter" />
|
||||
</UserControl.Resources>
|
||||
<StackPanel>
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Latest release</TextBlock>
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Releases</TextBlock>
|
||||
<Border Classes="card-separator" />
|
||||
<Button HorizontalAlignment="Stretch"
|
||||
|
||||
<Button Margin="0 0 0 5"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Command="{CompiledBinding DownloadLatestRelease}">
|
||||
Command="{CompiledBinding NavigateToRelease}"
|
||||
CommandParameter="{CompiledBinding LatestRelease}">
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<!-- Icon -->
|
||||
<Border Grid.Column="0"
|
||||
CornerRadius="4"
|
||||
Background="{StaticResource SystemAccentColor}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 6"
|
||||
Width="50"
|
||||
Height="50"
|
||||
ClipToBounds="True">
|
||||
<avalonia:MaterialIcon Kind="Download"></avalonia:MaterialIcon>
|
||||
</Border>
|
||||
<avalonia:MaterialIcon Grid.Column="0"
|
||||
Margin="0 6"
|
||||
Width="50"
|
||||
Height="50"
|
||||
Kind="BoxStar">
|
||||
</avalonia:MaterialIcon>
|
||||
|
||||
<!-- Body -->
|
||||
<StackPanel Grid.Column="1" Margin="10 0" VerticalAlignment="Center">
|
||||
<TextBlock Text="{CompiledBinding Entry.LatestRelease.Version, FallbackValue=Version}"></TextBlock>
|
||||
<TextBlock Text="{CompiledBinding LatestRelease.Version, FallbackValue=Version}"></TextBlock>
|
||||
<TextBlock Classes="subtitle">
|
||||
<avalonia:MaterialIcon Kind="BoxOutline" />
|
||||
<Run Text="{CompiledBinding Entry.LatestRelease.DownloadSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay}"></Run>
|
||||
<avalonia:MaterialIcon Kind="File" />
|
||||
<Run Text="{CompiledBinding LatestRelease.DownloadSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay}"></Run>
|
||||
</TextBlock>
|
||||
<TextBlock Classes="subtitle"
|
||||
ToolTip.Tip="{CompiledBinding Entry.LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
||||
ToolTip.Tip="{CompiledBinding LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
||||
<avalonia:MaterialIcon Kind="Calendar" />
|
||||
<Run>Created</Run>
|
||||
<Run Text="{CompiledBinding Entry.LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
||||
<Run Text="{CompiledBinding LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<ItemsControl ItemsSource="{CompiledBinding OtherReleases}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="5" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="workshop:IRelease">
|
||||
<Button HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Command="{Binding $parent[details:EntryReleasesView].DataContext.NavigateToRelease}"
|
||||
CommandParameter="{CompiledBinding}">
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<avalonia:MaterialIcon Grid.Column="0"
|
||||
Margin="0 6"
|
||||
Width="25"
|
||||
Height="25"
|
||||
Kind="Archive">
|
||||
</avalonia:MaterialIcon>
|
||||
|
||||
<!-- Body -->
|
||||
<StackPanel Grid.Column="1" Margin="10 0" VerticalAlignment="Center">
|
||||
<TextBlock Text="{CompiledBinding Version, FallbackValue=Version}"></TextBlock>
|
||||
<TextBlock Classes="subtitle" ToolTip.Tip="{CompiledBinding CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
||||
<avalonia:MaterialIcon Kind="Calendar" />
|
||||
<Run>Created</Run>
|
||||
<Run Text="{CompiledBinding CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@ -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<IRelease>().ToList();
|
||||
|
||||
DownloadLatestRelease = ReactiveCommand.CreateFromTask(ExecuteDownloadLatestRelease);
|
||||
OnInstallationStarted = Confirm;
|
||||
NavigateToRelease = ReactiveCommand.CreateFromTask<IRelease>(ExecuteNavigateToRelease);
|
||||
}
|
||||
|
||||
public IEntryDetails Entry { get; }
|
||||
public ReactiveCommand<Unit, Unit> DownloadLatestRelease { get; }
|
||||
public IRelease? LatestRelease { get; }
|
||||
public List<IRelease> OtherReleases { get; }
|
||||
|
||||
public Func<IEntryDetails, Task<bool>> OnInstallationStarted { get; set; }
|
||||
public ReactiveCommand<Unit, Unit> DownloadLatestRelease { get; }
|
||||
public ReactiveCommand<IRelease, Unit> NavigateToRelease { get; }
|
||||
|
||||
public Func<IEntryDetails, IRelease, Task<bool>> OnInstallationStarted { get; set; }
|
||||
public Func<InstalledEntry, Task>? 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<StreamProgress>(), cancellationToken);
|
||||
EntryInstallResult result = await installationHandler.InstallAsync(Entry, LatestRelease, new Progress<StreamProgress>(), cancellationToken);
|
||||
if (result.IsSuccess && result.Entry != null)
|
||||
{
|
||||
if (OnInstallationFinished != null)
|
||||
@ -62,13 +80,13 @@ public class EntryReleasesViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> Confirm(IEntryDetails entryDetails)
|
||||
private async Task<bool> 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;
|
||||
}
|
||||
}
|
||||
@ -18,7 +18,7 @@ public partial class EntriesView : ReactiveUserControl<EntriesViewModel>
|
||||
|
||||
private void Navigate(ViewModelBase viewModel)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => TabFrame.NavigateFromObject(viewModel));
|
||||
TabFrame.NavigateFromObject(viewModel);
|
||||
}
|
||||
|
||||
private void NavigationView_OnBackRequested(object? sender, NavigationViewBackRequestedEventArgs e)
|
||||
|
||||
@ -53,8 +53,8 @@ public partial class EntriesViewModel : RoutableHostScreen<RoutableScreen>
|
||||
|
||||
public void GoBack()
|
||||
{
|
||||
if (ViewingDetails)
|
||||
_router.GoBack();
|
||||
if (ViewingDetails && SelectedTab != null)
|
||||
_router.Navigate(SelectedTab.Path);
|
||||
else
|
||||
_router.Navigate("workshop");
|
||||
}
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
<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:list="clr-namespace:Artemis.UI.Screens.Workshop.Entries.List"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Entries.List.EntryListView"
|
||||
x:DataType="list:EntryListViewModel">
|
||||
<UserControl.Styles>
|
||||
<Styles>
|
||||
<Style Selector="StackPanel.empty-state > TextBlock">
|
||||
<Setter Property="TextAlignment" Value="Center"></Setter>
|
||||
<Setter Property="TextWrapping" Value="Wrap"></Setter>
|
||||
</Style>
|
||||
</Styles>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid ColumnDefinitions="300,*" RowDefinitions="Auto,*,Auto">
|
||||
<StackPanel Grid.Column="0" Grid.RowSpan="3" Margin="0 0 10 0" VerticalAlignment="Top">
|
||||
<Border Classes="card" VerticalAlignment="Stretch">
|
||||
<StackPanel>
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Categories</TextBlock>
|
||||
<Border Classes="card-separator" />
|
||||
<ContentControl Content="{CompiledBinding CategoriesViewModel}"></ContentControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<ProgressBar Grid.Column="1" Grid.Row="0" VerticalAlignment="Top" Margin="0 0 20 0" IsVisible="{CompiledBinding FetchingMore}" IsIndeterminate="True" />
|
||||
<ContentControl Grid.Column="1" Grid.Row="0" Margin="0 0 20 8" Content="{CompiledBinding InputViewModel}" />
|
||||
|
||||
<ScrollViewer Name="EntriesScrollViewer" Grid.Column="1" Grid.Row="1" ScrollChanged="ScrollViewer_OnScrollChanged" Offset="{CompiledBinding ScrollOffset}">
|
||||
<ItemsControl ItemsSource="{CompiledBinding Entries}" Margin="0 0 20 0">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{CompiledBinding}" Margin="0 0 0 5"></ContentControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<Panel Grid.Column="1" Grid.Row="1" IsVisible="{CompiledBinding !Initializing}">
|
||||
<StackPanel IsVisible="{CompiledBinding !Entries.Count}" Margin="0 50 0 0" Classes="empty-state">
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">Looks like your current filters gave no results</TextBlock>
|
||||
<TextBlock>
|
||||
<Run>Modify or clear your filters to view other entries</Run>
|
||||
</TextBlock>
|
||||
<Lottie Path="/Assets/Animations/empty.json" RepeatCount="1" Width="350" Height="350"></Lottie>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -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<LayoutListViewModel>
|
||||
public partial class EntryListView : ReactiveUserControl<EntryListViewModel>
|
||||
{
|
||||
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()
|
||||
@ -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<RoutableScreen>
|
||||
public partial class EntryListViewModel : RoutableScreen
|
||||
{
|
||||
private readonly SourceList<IEntrySummary> _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<IEntrySummary, EntryListItemViewModel> getEntryListViewModel)
|
||||
{
|
||||
_route = route;
|
||||
_workshopClient = workshopClient;
|
||||
_notificationService = notificationService;
|
||||
|
||||
@ -50,37 +50,31 @@ public abstract partial class EntryListViewModel : RoutableHostScreen<RoutableSc
|
||||
.Subscribe();
|
||||
Entries = entries;
|
||||
|
||||
// Respond to filter query input changes
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
// 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<EntryListItemViewModel> 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<RoutableSc
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual EntryFilterInput GetFilter()
|
||||
private EntryFilterInput GetFilter()
|
||||
{
|
||||
return new EntryFilterInput {And = CategoriesViewModel.CategoryFilters};
|
||||
return new EntryFilterInput
|
||||
{
|
||||
And =
|
||||
[
|
||||
new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = EntryType}},
|
||||
..CategoriesViewModel.CategoryFilters ?? []
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual IReadOnlyList<EntrySortInput> GetSort()
|
||||
private IReadOnlyList<EntrySortInput> GetSort()
|
||||
{
|
||||
// Sort by created at
|
||||
if (InputViewModel.SortBy == 1)
|
||||
|
||||
@ -1,67 +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:tabs="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Tabs"
|
||||
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.Entries.Tabs.LayoutListView"
|
||||
x:DataType="tabs:LayoutListViewModel">
|
||||
<UserControl.Styles>
|
||||
<Styles>
|
||||
<Style Selector="StackPanel.empty-state > TextBlock">
|
||||
<Setter Property="TextAlignment" Value="Center"></Setter>
|
||||
<Setter Property="TextWrapping" Value="Wrap"></Setter>
|
||||
</Style>
|
||||
</Styles>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Panel>
|
||||
<Grid ColumnDefinitions="300,*" RowDefinitions="Auto,*,Auto" IsVisible="{CompiledBinding Screen, Converter={x:Static ObjectConverters.IsNull}}">
|
||||
<StackPanel Grid.Column="0" Grid.RowSpan="3" Margin="0 0 10 0" VerticalAlignment="Top">
|
||||
<Border Classes="card" VerticalAlignment="Stretch">
|
||||
<StackPanel>
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Categories</TextBlock>
|
||||
<Border Classes="card-separator" />
|
||||
<ContentControl Content="{CompiledBinding CategoriesViewModel}"></ContentControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<ProgressBar Grid.Column="1" Grid.Row="0" VerticalAlignment="Top" Margin="0 0 20 0" IsVisible="{CompiledBinding FetchingMore}" IsIndeterminate="True" />
|
||||
<ContentControl Grid.Column="1" Grid.Row="0" Margin="0 0 20 8" Content="{CompiledBinding InputViewModel}" />
|
||||
|
||||
<ScrollViewer Name="EntriesScrollViewer" Grid.Column="1" Grid.Row="1" ScrollChanged="ScrollViewer_OnScrollChanged">
|
||||
<ItemsControl ItemsSource="{CompiledBinding Entries}" Margin="0 0 20 0">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{CompiledBinding}" Margin="0 0 0 5"></ContentControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<Panel Grid.Column="1" Grid.Row="1" IsVisible="{CompiledBinding !Initializing}">
|
||||
<StackPanel IsVisible="{CompiledBinding !Entries.Count}" Margin="0 50 0 0" Classes="empty-state">
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">Looks like your current filters gave no results</TextBlock>
|
||||
<TextBlock>
|
||||
<Run>Modify or clear your filters to view other device layouts</Run>
|
||||
</TextBlock>
|
||||
<Lottie Path="/Assets/Animations/empty.json" RepeatCount="1" Width="350" Height="350"></Lottie>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Grid>
|
||||
|
||||
<controls:Frame Name="RouterFrame" IsNavigationStackEnabled="False" CacheSize="0" IsVisible="{CompiledBinding Screen, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<controls:Frame.NavigationPageFactory>
|
||||
<ui:PageFactory />
|
||||
</controls:Frame.NavigationPageFactory>
|
||||
</controls:Frame>
|
||||
</Panel>
|
||||
</UserControl>
|
||||
@ -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<IEntrySummary, EntryListItemViewModel> 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()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,67 +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:tabs="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Tabs"
|
||||
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.Entries.Tabs.PluginListView"
|
||||
x:DataType="tabs:PluginListViewModel">
|
||||
<UserControl.Styles>
|
||||
<Styles>
|
||||
<Style Selector="StackPanel.empty-state > TextBlock">
|
||||
<Setter Property="TextAlignment" Value="Center"></Setter>
|
||||
<Setter Property="TextWrapping" Value="Wrap"></Setter>
|
||||
</Style>
|
||||
</Styles>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Panel>
|
||||
<Grid ColumnDefinitions="300,*" RowDefinitions="Auto,*,Auto" IsVisible="{CompiledBinding Screen, Converter={x:Static ObjectConverters.IsNull}}">
|
||||
<StackPanel Grid.Column="0" Grid.RowSpan="3" Margin="0 0 10 0" VerticalAlignment="Top">
|
||||
<Border Classes="card" VerticalAlignment="Stretch">
|
||||
<StackPanel>
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Categories</TextBlock>
|
||||
<Border Classes="card-separator" />
|
||||
<ContentControl Content="{CompiledBinding CategoriesViewModel}"></ContentControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<ProgressBar Grid.Column="1" Grid.Row="0" VerticalAlignment="Top" Margin="0 0 20 0" IsVisible="{CompiledBinding FetchingMore}" IsIndeterminate="True" />
|
||||
<ContentControl Grid.Column="1" Grid.Row="0" Margin="0 0 20 8" Content="{CompiledBinding InputViewModel}" />
|
||||
|
||||
<ScrollViewer Name="EntriesScrollViewer" Grid.Column="1" Grid.Row="1" ScrollChanged="ScrollViewer_OnScrollChanged">
|
||||
<ItemsControl ItemsSource="{CompiledBinding Entries}" Margin="0 0 20 0">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{CompiledBinding}" Margin="0 0 0 5"></ContentControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<Panel Grid.Column="1" Grid.Row="1" IsVisible="{CompiledBinding !Initializing}">
|
||||
<StackPanel IsVisible="{CompiledBinding !Entries.Count}" Margin="0 50 0 0" Classes="empty-state">
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">Looks like your current filters gave no results</TextBlock>
|
||||
<TextBlock>
|
||||
<Run>Modify or clear your filters to view other plugins</Run>
|
||||
</TextBlock>
|
||||
<Lottie Path="/Assets/Animations/empty.json" RepeatCount="1" Width="350" Height="350"></Lottie>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Grid>
|
||||
|
||||
<controls:Frame Name="RouterFrame" IsNavigationStackEnabled="False" CacheSize="0" IsVisible="{CompiledBinding Screen, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<controls:Frame.NavigationPageFactory>
|
||||
<ui:PageFactory />
|
||||
</controls:Frame.NavigationPageFactory>
|
||||
</controls:Frame>
|
||||
</Panel>
|
||||
</UserControl>
|
||||
@ -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<PluginListViewModel>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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<IEntrySummary, EntryListItemViewModel> 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()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,67 +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:tabs="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Tabs"
|
||||
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.Entries.Tabs.ProfileListView"
|
||||
x:DataType="tabs:ProfileListViewModel">
|
||||
<UserControl.Styles>
|
||||
<Styles>
|
||||
<Style Selector="StackPanel.empty-state > TextBlock">
|
||||
<Setter Property="TextAlignment" Value="Center"></Setter>
|
||||
<Setter Property="TextWrapping" Value="Wrap"></Setter>
|
||||
</Style>
|
||||
</Styles>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Panel>
|
||||
<Grid ColumnDefinitions="300,*" RowDefinitions="Auto,*,Auto" IsVisible="{CompiledBinding Screen, Converter={x:Static ObjectConverters.IsNull}}">
|
||||
<StackPanel Grid.Column="0" Grid.RowSpan="3" Margin="0 0 10 0" VerticalAlignment="Top">
|
||||
<Border Classes="card" VerticalAlignment="Stretch">
|
||||
<StackPanel>
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Categories</TextBlock>
|
||||
<Border Classes="card-separator" />
|
||||
<ContentControl Content="{CompiledBinding CategoriesViewModel}"></ContentControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<ProgressBar Grid.Column="1" Grid.Row="0" VerticalAlignment="Top" Margin="0 0 20 0" IsVisible="{CompiledBinding FetchingMore}" IsIndeterminate="True" />
|
||||
<ContentControl Grid.Column="1" Grid.Row="0" Margin="0 0 20 8" Content="{CompiledBinding InputViewModel}" />
|
||||
|
||||
<ScrollViewer Name="EntriesScrollViewer" Grid.Column="1" Grid.Row="1" ScrollChanged="ScrollViewer_OnScrollChanged">
|
||||
<ItemsControl ItemsSource="{CompiledBinding Entries}" Margin="0 0 20 0">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{CompiledBinding}" Margin="0 0 0 5"></ContentControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<Panel Grid.Column="1" Grid.Row="1" IsVisible="{CompiledBinding !Initializing}">
|
||||
<StackPanel IsVisible="{CompiledBinding !Entries.Count}" Margin="0 50 0 0" Classes="empty-state">
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">Looks like your current filters gave no results</TextBlock>
|
||||
<TextBlock>
|
||||
<Run>Modify or clear your filters to view some awesome profiles</Run>
|
||||
</TextBlock>
|
||||
<Lottie Path="/Assets/Animations/empty.json" RepeatCount="1" Width="350" Height="350"></Lottie>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Grid>
|
||||
|
||||
<controls:Frame Name="RouterFrame" IsNavigationStackEnabled="False" CacheSize="0" IsVisible="{CompiledBinding Screen, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<controls:Frame.NavigationPageFactory>
|
||||
<ui:PageFactory />
|
||||
</controls:Frame.NavigationPageFactory>
|
||||
</controls:Frame>
|
||||
</Panel>
|
||||
</UserControl>
|
||||
@ -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<ProfileListViewModel>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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<IEntrySummary, EntryListItemViewModel> 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()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@
|
||||
<Border Classes="card" VerticalAlignment="Top">
|
||||
<ContentControl Content="{CompiledBinding EntryInfoViewModel}" />
|
||||
</Border>
|
||||
<Border Classes="card" VerticalAlignment="Top" IsVisible="{CompiledBinding Entry.LatestRelease, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<Border Classes="card" VerticalAlignment="Top" IsVisible="{CompiledBinding Entry.Releases.Count}">
|
||||
<ContentControl Content="{CompiledBinding EntryReleasesViewModel}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
16
src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml
Normal file
16
src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml
Normal file
@ -0,0 +1,16 @@
|
||||
<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:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:ui="clr-namespace:Artemis.UI"
|
||||
xmlns:layout="clr-namespace:Artemis.UI.Screens.Workshop.Layout"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Layout.LayoutListView"
|
||||
x:DataType="layout:LayoutListViewModel">
|
||||
<controls:Frame Name="RouterFrame" IsNavigationStackEnabled="False" CacheSize="0">
|
||||
<controls:Frame.NavigationPageFactory>
|
||||
<ui:PageFactory />
|
||||
</controls:Frame.NavigationPageFactory>
|
||||
</controls:Frame>
|
||||
</UserControl>
|
||||
@ -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<LayoutListViewModel>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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<RoutableScreen>
|
||||
{
|
||||
public EntryListViewModel EntryListViewModel { get; }
|
||||
|
||||
public LayoutListViewModel(EntryListViewModel entryListViewModel)
|
||||
{
|
||||
EntryListViewModel = entryListViewModel;
|
||||
EntryListViewModel.EntryType = EntryType.Layout;
|
||||
}
|
||||
}
|
||||
@ -18,9 +18,9 @@ public partial class WorkshopLibraryView : ReactiveUserControl<WorkshopLibraryVi
|
||||
|
||||
private void Navigate(ViewModelBase viewModel)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => TabFrame.NavigateFromObject(viewModel));
|
||||
TabFrame.NavigateFromObject(viewModel);
|
||||
}
|
||||
|
||||
|
||||
private void NavigationView_OnBackRequested(object? sender, NavigationViewBackRequestedEventArgs e)
|
||||
{
|
||||
ViewModel?.GoBack();
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Classes="card" VerticalAlignment="Top" IsVisible="{CompiledBinding Entry.LatestRelease, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<Border Classes="card" VerticalAlignment="Top" IsVisible="{CompiledBinding Entry.Releases.Count}">
|
||||
<ContentControl Content="{CompiledBinding EntryReleasesViewModel}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
@ -49,7 +49,13 @@
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Used by these profiles</TextBlock>
|
||||
<Border Classes="card-separator" />
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{CompiledBinding Dependants}"></ItemsControl>
|
||||
<ItemsControl ItemsSource="{CompiledBinding Dependants}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="5"></StackPanel>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@ -33,7 +33,7 @@ public partial class PluginDetailsViewModel : RoutableScreen<WorkshopDetailParam
|
||||
[Notify] private EntryReleasesViewModel? _entryReleasesViewModel;
|
||||
[Notify] private EntryImagesViewModel? _entryImagesViewModel;
|
||||
[Notify] private ReadOnlyObservableCollection<EntryListItemViewModel>? _dependants;
|
||||
|
||||
|
||||
public PluginDetailsViewModel(IWorkshopClient client,
|
||||
IWindowService windowService,
|
||||
IPluginManagementService pluginManagementService,
|
||||
@ -72,18 +72,21 @@ public partial class PluginDetailsViewModel : RoutableScreen<WorkshopDetailParam
|
||||
EntryReleasesViewModel.OnInstallationStarted = OnInstallationStarted;
|
||||
EntryReleasesViewModel.OnInstallationFinished = OnInstallationFinished;
|
||||
}
|
||||
|
||||
|
||||
IReadOnlyList<IEntrySummary>? dependants = (await _client.GetDependantEntries.ExecuteAsync(entryId, 0, 25, cancellationToken)).Data?.Entries?.Items;
|
||||
Dependants = dependants != null && dependants.Any()
|
||||
? new ReadOnlyObservableCollection<EntryListItemViewModel>(new ObservableCollection<EntryListItemViewModel>(dependants.Select(_getEntryListViewModel)))
|
||||
? new ReadOnlyObservableCollection<EntryListItemViewModel>(new ObservableCollection<EntryListItemViewModel>(dependants
|
||||
.Select(_getEntryListViewModel)
|
||||
.OrderByDescending(d => d.Entry.Downloads)
|
||||
.Take(10)))
|
||||
: null;
|
||||
}
|
||||
|
||||
private async Task<bool> OnInstallationStarted(IEntryDetails entryDetails)
|
||||
private async Task<bool> 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"
|
||||
);
|
||||
|
||||
16
src/Artemis.UI/Screens/Workshop/Plugins/PluginListView.axaml
Normal file
16
src/Artemis.UI/Screens/Workshop/Plugins/PluginListView.axaml
Normal file
@ -0,0 +1,16 @@
|
||||
<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:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:ui="clr-namespace:Artemis.UI"
|
||||
xmlns:plugins="clr-namespace:Artemis.UI.Screens.Workshop.Plugins"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Plugins.PluginListView"
|
||||
x:DataType="plugins:PluginListViewModel">
|
||||
<controls:Frame Name="RouterFrame" IsNavigationStackEnabled="False" CacheSize="0">
|
||||
<controls:Frame.NavigationPageFactory>
|
||||
<ui:PageFactory />
|
||||
</controls:Frame.NavigationPageFactory>
|
||||
</controls:Frame>
|
||||
</UserControl>
|
||||
@ -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<PluginListViewModel>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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<RoutableScreen>
|
||||
{
|
||||
public EntryListViewModel EntryListViewModel { get; }
|
||||
|
||||
public PluginListViewModel(EntryListViewModel entryListViewModel)
|
||||
{
|
||||
EntryListViewModel = entryListViewModel;
|
||||
EntryListViewModel.EntryType = EntryType.Plugin;
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@
|
||||
<Border Classes="card" VerticalAlignment="Top">
|
||||
<ContentControl Content="{CompiledBinding EntryInfoViewModel}" />
|
||||
</Border>
|
||||
<Border Classes="card" VerticalAlignment="Top" IsVisible="{CompiledBinding Entry.LatestRelease, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<Border Classes="card" VerticalAlignment="Top" IsVisible="{CompiledBinding Entry.Releases.Count}">
|
||||
<ContentControl Content="{CompiledBinding EntryReleasesViewModel}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
@ -32,7 +32,13 @@
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Required plugins</TextBlock>
|
||||
<Border Classes="card-separator" />
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{CompiledBinding Dependencies}"></ItemsControl>
|
||||
<ItemsControl ItemsSource="{CompiledBinding Dependencies}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="5"></StackPanel>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
<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:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:ui="clr-namespace:Artemis.UI"
|
||||
xmlns:profile="clr-namespace:Artemis.UI.Screens.Workshop.Profile"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Profile.ProfileListView"
|
||||
x:DataType="profile:ProfileListViewModel">
|
||||
<controls:Frame Name="RouterFrame" IsNavigationStackEnabled="False" CacheSize="0">
|
||||
<controls:Frame.NavigationPageFactory>
|
||||
<ui:PageFactory />
|
||||
</controls:Frame.NavigationPageFactory>
|
||||
</controls:Frame>
|
||||
</UserControl>
|
||||
@ -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<ProfileListViewModel>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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<RoutableScreen>
|
||||
{
|
||||
public EntryListViewModel EntryListViewModel { get; }
|
||||
|
||||
public ProfileListViewModel(EntryListViewModel entryListViewModel)
|
||||
{
|
||||
EntryListViewModel = entryListViewModel;
|
||||
EntryListViewModel.EntryType = EntryType.Profile;
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@ using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.SubmissionWizard;
|
||||
|
||||
public partial class ReleaseWizardView: ReactiveAppWindow<ReleaseWizardViewModel>
|
||||
public partial class ReleaseWizardView : ReactiveAppWindow<ReleaseWizardViewModel>
|
||||
{
|
||||
public ReleaseWizardView()
|
||||
{
|
||||
@ -25,7 +25,7 @@ public partial class ReleaseWizardView: ReactiveAppWindow<ReleaseWizardViewModel
|
||||
{
|
||||
try
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => Frame.NavigateFromObject(viewModel));
|
||||
Frame.NavigateFromObject(viewModel);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@ -25,7 +25,7 @@ public partial class SubmissionWizardView : ReactiveAppWindow<SubmissionWizardVi
|
||||
{
|
||||
try
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => Frame.NavigateFromObject(viewModel));
|
||||
Frame.NavigateFromObject(viewModel);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@ -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">
|
||||
<Design.PreviewWith>
|
||||
<avalonia:MarkdownScrollViewer>
|
||||
Test
|
||||
</avalonia:MarkdownScrollViewer>
|
||||
</Design.PreviewWith>
|
||||
<Style Selector="ScrollViewer > StackPanel">
|
||||
<Setter Property="Margin" Value="0 0 15 0"></Setter>
|
||||
</Style>
|
||||
|
||||
@ -54,12 +54,12 @@ fragment entryDetails on Entry {
|
||||
categories {
|
||||
...category
|
||||
}
|
||||
latestRelease {
|
||||
...release
|
||||
}
|
||||
images {
|
||||
...image
|
||||
}
|
||||
releases {
|
||||
...release
|
||||
}
|
||||
}
|
||||
|
||||
fragment release on Release {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user