1
0
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:
Robert 2024-03-24 21:29:48 +01:00
parent da3d47d7b8
commit 9c04932afa
39 changed files with 419 additions and 498 deletions

View File

@ -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>

View File

@ -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;

View File

@ -19,7 +19,7 @@ public partial class RootView : ReactiveUserControl<RootViewModel>
{
try
{
Dispatcher.UIThread.Invoke(() => RootFrame.NavigateFromObject(viewModel));
RootFrame.NavigateFromObject(viewModel);
}
catch (Exception)
{

View File

@ -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)

View File

@ -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
}
}
}

View File

@ -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()

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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)

View File

@ -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");
}

View File

@ -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>

View File

@ -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()

View File

@ -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)

View File

@ -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>

View File

@ -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()
}
};
}
}

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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()
}
};
}
}

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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()
}
};
}
}

View File

@ -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>

View 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>

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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>

View File

@ -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"
);

View 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>

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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>

View 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: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>

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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)
{

View File

@ -25,7 +25,7 @@ public partial class SubmissionWizardView : ReactiveAppWindow<SubmissionWizardVi
{
try
{
Dispatcher.UIThread.Invoke(() => Frame.NavigateFromObject(viewModel));
Frame.NavigateFromObject(viewModel);
}
catch (Exception e)
{

View File

@ -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>

View File

@ -54,12 +54,12 @@ fragment entryDetails on Entry {
categories {
...category
}
latestRelease {
...release
}
images {
...image
}
releases {
...release
}
}
fragment release on Release {