mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
Workshop - Redesigned library
Workshop - Limit screen width to keep main content at 1000px Workshop - Auto-updating WIP
This commit is contained in:
1 parent
4552b3ba17
commit
99d11e1921
37 files changed
+955
-200
No files matched your search
+2
-2
@@ -36,7 +36,7 @@ public partial class WorkshopLayoutViewModel : ActivatableViewModelBase, ILayout
|
||||
Entries = new ObservableCollection<InstalledEntry>(workshopService.GetInstalledEntries().Where(e => e.EntryType == EntryType.Layout));
|
||||
|
||||
this.WhenAnyValue(vm => vm.SelectedEntry).Subscribe(ApplyEntry);
|
||||
this.WhenActivated((CompositeDisposable _) => SelectedEntry = Entries.FirstOrDefault(e => e.EntryId.ToString() == Device.LayoutSelection.Parameter));
|
||||
this.WhenActivated((CompositeDisposable _) => SelectedEntry = Entries.FirstOrDefault(e => e.Id.ToString() == Device.LayoutSelection.Parameter));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -70,7 +70,7 @@ public partial class WorkshopLayoutViewModel : ActivatableViewModelBase, ILayout
|
||||
|
||||
private void ApplyEntry(InstalledEntry? entry)
|
||||
{
|
||||
if (entry == null || Device.LayoutSelection.Parameter == entry.EntryId.ToString())
|
||||
if (entry == null || Device.LayoutSelection.Parameter == entry.Id.ToString())
|
||||
return;
|
||||
_layoutProvider.ConfigureDevice(Device, entry);
|
||||
Save();
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:home="clr-namespace:Artemis.UI.Screens.Home"
|
||||
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="900"
|
||||
x:Class="Artemis.UI.Screens.Home.HomeView">
|
||||
x:Class="Artemis.UI.Screens.Home.HomeView"
|
||||
x:DataType="home:HomeViewModel">
|
||||
<Border Classes="router-container">
|
||||
<Grid RowDefinitions="200,*">
|
||||
<Image Grid.Row="0"
|
||||
@@ -40,7 +42,7 @@
|
||||
Under Settings > Plugins you can find your currently installed plugins, these default plugins are created by Artemis developers. We're also keeping track of a list of third-party plugins on our wiki.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<controls:HyperlinkButton Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins?mtm_campaign=artemis&mtm_kwd=home" HorizontalAlignment="Right">
|
||||
<controls:HyperlinkButton Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" HorizontalAlignment="Right" Command="{CompiledBinding GetMorePlugins}">
|
||||
<controls:HyperlinkButton.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Test"></MenuItem>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Artemis.Core.Services;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Screens.StartupWizard;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
@@ -9,12 +10,20 @@ namespace Artemis.UI.Screens.Home;
|
||||
|
||||
public class HomeViewModel : RoutableScreen, IMainScreenViewModel
|
||||
{
|
||||
public HomeViewModel(ISettingsService settingsService, IWindowService windowService)
|
||||
private readonly IRouter _router;
|
||||
|
||||
public HomeViewModel(IRouter router, ISettingsService settingsService, IWindowService windowService)
|
||||
{
|
||||
_router = router;
|
||||
// Show the startup wizard if it hasn't been completed
|
||||
if (!settingsService.GetSetting("UI.SetupWizardCompleted", false).Value)
|
||||
Dispatcher.UIThread.InvokeAsync(async () => await windowService.ShowDialogAsync<StartupWizardViewModel, bool>());
|
||||
}
|
||||
|
||||
public ViewModelBase? TitleBarViewModel => null;
|
||||
|
||||
public async Task GetMorePlugins()
|
||||
{
|
||||
await _router.Navigate("workshop/entries/plugins");
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
<TextBox Classes="clearButton" Text="{CompiledBinding SearchPluginInput}" Watermark="Search plugins" Margin="0 0 10 0" />
|
||||
|
||||
<StackPanel Spacing="5" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<controls:HyperlinkButton VerticalAlignment="Top" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins?mtm_campaign=artemis&mtm_kwd=plugins">
|
||||
<controls:HyperlinkButton VerticalAlignment="Top" Command="{CompiledBinding GetMorePlugins}">
|
||||
Get more plugins
|
||||
</controls:HyperlinkButton>
|
||||
<Button Classes="accent" Command="{CompiledBinding ImportPlugin}">Import plugin</Button>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
@@ -9,5 +10,4 @@ public partial class PluginsTabView : ReactiveUserControl<PluginsTabViewModel>
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,12 +23,14 @@ namespace Artemis.UI.Screens.Settings;
|
||||
public partial class PluginsTabViewModel : RoutableScreen
|
||||
{
|
||||
private readonly INotificationService _notificationService;
|
||||
private readonly IRouter _router;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly IWindowService _windowService;
|
||||
[Notify] private string? _searchPluginInput;
|
||||
|
||||
public PluginsTabViewModel(IPluginManagementService pluginManagementService, INotificationService notificationService, IWindowService windowService, ISettingsVmFactory settingsVmFactory)
|
||||
public PluginsTabViewModel(IRouter router, IPluginManagementService pluginManagementService, INotificationService notificationService, IWindowService windowService, ISettingsVmFactory settingsVmFactory)
|
||||
{
|
||||
_router = router;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_notificationService = notificationService;
|
||||
_windowService = windowService;
|
||||
@@ -113,4 +115,9 @@ public partial class PluginsTabViewModel : RoutableScreen
|
||||
return data => data.Info.Name.Contains(text, StringComparison.InvariantCultureIgnoreCase) ||
|
||||
(data.Info.Description != null && data.Info.Description.Contains(text, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
public async Task GetMorePlugins()
|
||||
{
|
||||
await _router.Navigate("workshop/entries/plugins");
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,17 @@
|
||||
</Styles>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,*,Auto">
|
||||
<StackPanel Grid.Column="0" Grid.RowSpan="3" Margin="0 0 10 0" VerticalAlignment="Top" Width="300" IsVisible="{CompiledBinding ShowCategoryFilter}">
|
||||
<Grid RowDefinitions="Auto,*,Auto" HorizontalAlignment="Stretch" MaxWidth="1330">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" Grid.RowSpan="3"
|
||||
Margin="0 0 10 0"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right"
|
||||
Width="300"
|
||||
IsVisible="{CompiledBinding ShowCategoryFilter}">
|
||||
<Border Classes="card" VerticalAlignment="Stretch">
|
||||
<StackPanel>
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Categories</TextBlock>
|
||||
@@ -27,7 +36,7 @@
|
||||
</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}" />
|
||||
<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">
|
||||
@@ -38,7 +47,7 @@
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{CompiledBinding}" Margin="0 0 0 5"></ContentControl>
|
||||
<ContentControl Content="{CompiledBinding}" Margin="0 0 0 8"></ContentControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Layout.LayoutDetailsView"
|
||||
x:DataType="layout:LayoutDetailsViewModel">
|
||||
<Grid ColumnDefinitions="300,*, 300" RowDefinitions="Auto,*">
|
||||
<Grid RowDefinitions="Auto,*" ColumnDefinitions="300,*,300" MaxWidth="1600" HorizontalAlignment="Stretch">
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" Spacing="10">
|
||||
<Border Classes="card" VerticalAlignment="Top">
|
||||
<ContentControl Content="{CompiledBinding EntryInfoViewModel}" />
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Layout.LayoutListDefaultView"
|
||||
x:DataType="layout:LayoutListDefaultViewModel">
|
||||
<Grid ColumnDefinitions="400,*">
|
||||
<Grid ColumnDefinitions="400,*" MaxWidth="1420">
|
||||
<Border Grid.Column="0" Classes="card" Margin="0 0 10 0" VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<DockPanel>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Artemis.UI.Screens.Workshop.Entries.List;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.WebClient.Workshop;
|
||||
|
||||
@@ -6,7 +5,6 @@ namespace Artemis.UI.Screens.Workshop.Layout;
|
||||
|
||||
public class LayoutListViewModel : RoutableHostScreen<RoutableScreen>
|
||||
{
|
||||
private readonly EntryListViewModel _entryListViewModel;
|
||||
public override RoutableScreen DefaultScreen { get; }
|
||||
|
||||
public LayoutListViewModel(LayoutListDefaultViewModel defaultViewModel)
|
||||
|
||||
@@ -19,13 +19,14 @@ namespace Artemis.UI.Screens.Workshop.LayoutFinder;
|
||||
public partial class LayoutFinderViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly SourceList<IRGBDeviceInfo> _devices;
|
||||
[Notify] private ReadOnlyObservableCollection<LayoutFinderDeviceViewModel> _deviceViewModels;
|
||||
|
||||
public LayoutFinderViewModel(ILogger logger, IDeviceService deviceService, Func<ArtemisDevice, LayoutFinderDeviceViewModel> getDeviceViewModel)
|
||||
{
|
||||
_logger = logger;
|
||||
SearchAll = ReactiveCommand.CreateFromTask(ExecuteSearchAll);
|
||||
DeviceViewModels = new ReadOnlyObservableCollection<LayoutFinderDeviceViewModel>([]);
|
||||
|
||||
this.WhenActivated((CompositeDisposable _) =>
|
||||
{
|
||||
IEnumerable<LayoutFinderDeviceViewModel> deviceGroups = deviceService.EnabledDevices.Select(getDeviceViewModel);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
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.Library.Tabs"
|
||||
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
@@ -13,47 +13,85 @@
|
||||
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
|
||||
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
||||
</UserControl.Resources>
|
||||
<Button MinHeight="65"
|
||||
MaxHeight="110"
|
||||
Padding="6"
|
||||
Margin="0 0 0 5"
|
||||
<Button MinHeight="110"
|
||||
MaxHeight="140"
|
||||
Padding="12"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Command="{CompiledBinding ViewWorkshopPage}">
|
||||
<Grid ColumnDefinitions="Auto,2*,*,*,*,Auto">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" RowDefinitions="*, Auto">
|
||||
<!-- Icon -->
|
||||
<Border Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
CornerRadius="6"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 10 0"
|
||||
Width="50"
|
||||
Height="50"
|
||||
Width="80"
|
||||
Height="80"
|
||||
ClipToBounds="True">
|
||||
<Image Stretch="UniformToFill" asyncImageLoader:ImageLoader.Source="{CompiledBinding InstalledEntry.EntryId, Converter={StaticResource EntryIconUriConverter}, Mode=OneWay}" />
|
||||
<Image Stretch="UniformToFill" il:ImageLoader.Source="{CompiledBinding Entry.Id, Converter={StaticResource EntryIconUriConverter}, Mode=OneWay}" />
|
||||
</Border>
|
||||
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock TextTrimming="CharacterEllipsis"
|
||||
Text="{CompiledBinding InstalledEntry.Name, FallbackValue=Title}" />
|
||||
<TextBlock Classes="subtitle"
|
||||
<!-- Body -->
|
||||
<Grid Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Row="0" Margin="0 0 0 5" TextTrimming="CharacterEllipsis">
|
||||
<Run Classes="h5" Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
|
||||
<Run Classes="subtitle">by</Run>
|
||||
<Run Classes="subtitle" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="1"
|
||||
Classes="subtitle"
|
||||
TextWrapping="Wrap"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Text="{CompiledBinding InstalledEntry.Author, FallbackValue=Summary}">
|
||||
Text="{CompiledBinding Entry.Summary, FallbackValue=Summary}">
|
||||
</TextBlock>
|
||||
|
||||
<ItemsControl Grid.Row="2" ItemsSource="{CompiledBinding Entry.Categories}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8"></StackPanel>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Margin="0 0 3 0"></avalonia:MaterialIcon>
|
||||
<TextBlock Text="{CompiledBinding Name}" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
|
||||
<!-- Info -->
|
||||
<StackPanel Grid.Column="2" Grid.Row="0" Margin="0 0 4 0" HorizontalAlignment="Right">
|
||||
<TextBlock TextAlignment="Right" Text="{CompiledBinding Entry.CreatedAt, FallbackValue=01-01-1337, Converter={StaticResource DateTimeConverter}}" />
|
||||
<TextBlock TextAlignment="Right">
|
||||
<avalonia:MaterialIcon Kind="Downloads" />
|
||||
<Run Classes="h5" Text="{CompiledBinding Entry.Downloads, FallbackValue=0}" />
|
||||
<Run>downloads</Run>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{CompiledBinding InstalledEntry.EntryType}"></TextBlock>
|
||||
<TextBlock Grid.Column="3" VerticalAlignment="Center" Text="{CompiledBinding InstalledEntry.ReleaseVersion}"></TextBlock>
|
||||
<TextBlock Grid.Column="4" VerticalAlignment="Center">
|
||||
<Run>Installed</Run>
|
||||
<Run Text="{CompiledBinding InstalledEntry.InstalledAt, FallbackValue=01-01-1337, Mode=OneWay, Converter={StaticResource DateTimeConverter}}" />
|
||||
</TextBlock>
|
||||
|
||||
<StackPanel Grid.Column="5" VerticalAlignment="Center" Orientation="Horizontal" Spacing="6">
|
||||
<Button Command="{CompiledBinding ViewLocal}">Open</Button>
|
||||
<Button Command="{CompiledBinding Uninstall}" Theme="{StaticResource TransparentButton}" Height="32">
|
||||
<avalonia:MaterialIcon Kind="Trash"/>
|
||||
</Button>
|
||||
|
||||
<!-- Install state -->
|
||||
<StackPanel Grid.Column="2" Grid.Row="1" Margin="0 0 4 0" HorizontalAlignment="Right" VerticalAlignment="Bottom">
|
||||
<TextBlock TextAlignment="Right" IsVisible="{CompiledBinding UpdateAvailable}">
|
||||
<avalonia:MaterialIcon Kind="Update" Foreground="{DynamicResource SystemAccentColorLight1}" Width="20" Height="20" />
|
||||
<Run>update available</Run>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Management -->
|
||||
<Border Grid.Column="3" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0">
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||
<Button Command="{CompiledBinding ViewLocal}" HorizontalAlignment="Stretch" >Open</Button>
|
||||
<Button Command="{CompiledBinding Uninstall}" HorizontalAlignment="Stretch">Uninstall</Button>
|
||||
</StackPanel>
|
||||
<CheckBox MinHeight="26" Margin="0 4 0 0" IsEnabled="False">Auto-update</CheckBox>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Button>
|
||||
</UserControl>
|
||||
@@ -1,10 +1,11 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
|
||||
|
||||
public partial class InstalledTabItemView : UserControl
|
||||
public partial class InstalledTabItemView : ReactiveUserControl<InstalledTabItemViewModel>
|
||||
{
|
||||
public InstalledTabItemView()
|
||||
{
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.DryIoc.Factories;
|
||||
using Artemis.UI.Extensions;
|
||||
using Artemis.UI.Screens.Plugins;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
@@ -15,70 +17,89 @@ using Artemis.WebClient.Workshop.Models;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
using StrawberryShake;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
|
||||
|
||||
public partial class InstalledTabItemViewModel : ViewModelBase
|
||||
public partial class InstalledTabItemViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly IWorkshopClient _client;
|
||||
private readonly IWorkshopService _workshopService;
|
||||
private readonly IRouter _router;
|
||||
private readonly IWindowService _windowService;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly ISettingsVmFactory _settingsVmFactory;
|
||||
|
||||
public InstalledTabItemViewModel(InstalledEntry installedEntry,
|
||||
[Notify] private bool _updateAvailable;
|
||||
|
||||
public InstalledTabItemViewModel(InstalledEntry entry,
|
||||
IWorkshopClient client,
|
||||
IWorkshopService workshopService,
|
||||
IRouter router,
|
||||
IRouter router,
|
||||
IWindowService windowService,
|
||||
IPluginManagementService pluginManagementService,
|
||||
ISettingsVmFactory settingsVmFactory)
|
||||
{
|
||||
_client = client;
|
||||
_workshopService = workshopService;
|
||||
_router = router;
|
||||
_windowService = windowService;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_settingsVmFactory = settingsVmFactory;
|
||||
InstalledEntry = installedEntry;
|
||||
Entry = entry;
|
||||
|
||||
ViewWorkshopPage = ReactiveCommand.CreateFromTask(ExecuteViewWorkshopPage);
|
||||
ViewLocal = ReactiveCommand.CreateFromTask(ExecuteViewLocal);
|
||||
Uninstall = ReactiveCommand.CreateFromTask(ExecuteUninstall);
|
||||
}
|
||||
|
||||
public InstalledEntry InstalledEntry { get; }
|
||||
public ReactiveCommand<Unit, Unit> ViewWorkshopPage { get; }
|
||||
public ReactiveCommand<Unit,Unit> ViewLocal { get; }
|
||||
public ReactiveCommand<Unit, Unit> Uninstall { get; }
|
||||
|
||||
private async Task ExecuteViewWorkshopPage()
|
||||
{
|
||||
await _workshopService.NavigateToEntry(InstalledEntry.EntryId, InstalledEntry.EntryType);
|
||||
}
|
||||
|
||||
private async Task ExecuteViewLocal(CancellationToken cancellationToken)
|
||||
{
|
||||
if (InstalledEntry.EntryType == EntryType.Profile && InstalledEntry.TryGetMetadata("ProfileId", out Guid profileId))
|
||||
this.WhenActivatedAsync(async _ =>
|
||||
{
|
||||
await _router.Navigate($"profile-editor/{profileId}");
|
||||
}
|
||||
// Grab the latest entry summary from the workshop
|
||||
try
|
||||
{
|
||||
IOperationResult<IGetEntrySummaryByIdResult> entrySummary = await _client.GetEntrySummaryById.ExecuteAsync(Entry.Id);
|
||||
if (entrySummary.Data?.Entry != null)
|
||||
{
|
||||
Entry.ApplyEntrySummary(entrySummary.Data.Entry);
|
||||
_workshopService.SaveInstalledEntry(Entry);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
UpdateAvailable = Entry.ReleaseId != Entry.LatestReleaseId;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task ExecuteUninstall(CancellationToken cancellationToken)
|
||||
|
||||
public InstalledEntry Entry { get; }
|
||||
|
||||
public async Task ViewWorkshopPage()
|
||||
{
|
||||
await _workshopService.NavigateToEntry(Entry.Id, Entry.EntryType);
|
||||
}
|
||||
|
||||
public async Task ViewLocal()
|
||||
{
|
||||
if (Entry.EntryType == EntryType.Profile && Entry.TryGetMetadata("ProfileId", out Guid profileId))
|
||||
await _router.Navigate($"profile-editor/{profileId}");
|
||||
else if (Entry.EntryType == EntryType.Plugin)
|
||||
await _router.Navigate($"workshop/entries/plugins/details/{Entry.Id}/manage");
|
||||
else if (Entry.EntryType == EntryType.Layout)
|
||||
await _router.Navigate($"workshop/entries/layouts/details/{Entry.Id}/manage");
|
||||
}
|
||||
|
||||
public async Task Uninstall()
|
||||
{
|
||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Do you want to uninstall this entry?", "Both the entry and its contents will be removed.");
|
||||
if (!confirmed)
|
||||
return;
|
||||
|
||||
// Ideally the installation handler does this but it doesn't have access to the required view models
|
||||
if (InstalledEntry.EntryType == EntryType.Plugin)
|
||||
if (Entry.EntryType == EntryType.Plugin)
|
||||
await UninstallPluginPrerequisites();
|
||||
|
||||
await _workshopService.UninstallEntry(InstalledEntry, cancellationToken);
|
||||
|
||||
await _workshopService.UninstallEntry(Entry, CancellationToken.None);
|
||||
}
|
||||
|
||||
private async Task UninstallPluginPrerequisites()
|
||||
{
|
||||
if (!InstalledEntry.TryGetMetadata("PluginId", out Guid pluginId))
|
||||
if (!Entry.TryGetMetadata("PluginId", out Guid pluginId))
|
||||
return;
|
||||
Plugin? plugin = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginId);
|
||||
if (plugin == null)
|
||||
|
||||
@@ -15,8 +15,16 @@
|
||||
</Styles>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Panel>
|
||||
<StackPanel IsVisible="{CompiledBinding Empty}" Margin="0 50 0 0" Classes="empty-state">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Grid Grid.Row="0" Grid.Column="0" MaxWidth="1000" Margin="0 22 0 10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="165" MaxWidth="400" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox Classes="search-box" Text="{CompiledBinding SearchEntryInput}" Watermark="Search library" Margin="0 0 10 0" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" IsVisible="{CompiledBinding Empty}" Margin="0 50 0 0" Classes="empty-state">
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">Not much here yet, huh!</TextBlock>
|
||||
<TextBlock>
|
||||
<Run>Any entries you download from the workshop you can later manage here</Run>
|
||||
@@ -24,21 +32,34 @@
|
||||
<Lottie Path="/Assets/Animations/empty.json" RepeatCount="1" Width="350" Height="350"></Lottie>
|
||||
<Button HorizontalAlignment="Center" Command="{CompiledBinding OpenWorkshop}">Browse the Workshop</Button>
|
||||
</StackPanel>
|
||||
|
||||
<ScrollViewer IsVisible="{CompiledBinding !Empty}">
|
||||
<ItemsControl ItemsSource="{CompiledBinding InstalledEntries}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{CompiledBinding}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
|
||||
<ScrollViewer Grid.Row="1" Grid.Column="0" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" VerticalAlignment="Top">
|
||||
<ItemsControl ItemsSource="{CompiledBinding EntryGroups}" MaxWidth="1000">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
<StackPanel Spacing="10"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}" Text="{Binding Key}" Margin="0 0 0 5"/>
|
||||
<ItemsControl ItemsSource="{Binding Items}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{Binding}" Margin="0 0 0 8" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Panel>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,14 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Artemis.WebClient.Workshop.Models;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using DynamicData.List;
|
||||
using Humanizer;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
|
||||
@@ -16,45 +18,45 @@ namespace Artemis.UI.Screens.Workshop.Library.Tabs;
|
||||
|
||||
public partial class InstalledTabViewModel : RoutableScreen
|
||||
{
|
||||
private SourceList<InstalledEntry> _installedEntries = new();
|
||||
|
||||
private SourceList<InstalledEntry> _entries = new();
|
||||
|
||||
[Notify] private string? _searchEntryInput;
|
||||
private readonly ObservableAsPropertyHelper<bool> _empty;
|
||||
|
||||
public InstalledTabViewModel(IWorkshopService workshopService, IRouter router, Func<InstalledEntry, InstalledTabItemViewModel> getInstalledTabItemViewModel)
|
||||
{
|
||||
IObservable<Func<InstalledEntry, bool>> pluginFilter = this.WhenAnyValue(vm => vm.SearchEntryInput).Throttle(TimeSpan.FromMilliseconds(100)).Select(CreatePredicate);
|
||||
IObservable<Func<InstalledEntry, bool>> searchFilter = this.WhenAnyValue(vm => vm.SearchEntryInput).Throttle(TimeSpan.FromMilliseconds(100)).Select(CreatePredicate);
|
||||
|
||||
_installedEntries.Connect()
|
||||
.Filter(pluginFilter)
|
||||
_entries.Connect()
|
||||
.Filter(searchFilter)
|
||||
.Sort(SortExpressionComparer<InstalledEntry>.Descending(p => p.InstalledAt))
|
||||
.Transform(getInstalledTabItemViewModel)
|
||||
.Bind(out ReadOnlyObservableCollection<InstalledTabItemViewModel> installedEntryViewModels)
|
||||
.GroupWithImmutableState(vm => vm.Entry.EntryType.Humanize(LetterCasing.Title).Pluralize())
|
||||
.Bind(out ReadOnlyObservableCollection<IGrouping<InstalledTabItemViewModel, string>> entryViewModels)
|
||||
.Subscribe();
|
||||
_empty = _entries.Connect().Count().Select(c => c == 0).ToProperty(this, vm => vm.Empty);
|
||||
_entries.AddRange(workshopService.GetInstalledEntries());
|
||||
|
||||
List<InstalledEntry> entries = workshopService.GetInstalledEntries();
|
||||
_installedEntries.AddRange(entries);
|
||||
|
||||
Empty = entries.Count == 0;
|
||||
InstalledEntries = installedEntryViewModels;
|
||||
|
||||
EntryGroups = entryViewModels;
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
workshopService.OnEntryUninstalled += WorkshopServiceOnOnEntryUninstalled;
|
||||
Disposable.Create(() => workshopService.OnEntryUninstalled -= WorkshopServiceOnOnEntryUninstalled).DisposeWith(d);
|
||||
});
|
||||
|
||||
|
||||
OpenWorkshop = ReactiveCommand.CreateFromTask(async () => await router.Navigate("workshop"));
|
||||
}
|
||||
|
||||
private void WorkshopServiceOnOnEntryUninstalled(object? sender, InstalledEntry e)
|
||||
{
|
||||
_installedEntries.Remove(e);
|
||||
_entries.Remove(e);
|
||||
}
|
||||
|
||||
public bool Empty { get; }
|
||||
public bool Empty => _empty.Value;
|
||||
public ReactiveCommand<Unit, Unit> OpenWorkshop { get; }
|
||||
public ReadOnlyObservableCollection<InstalledTabItemViewModel> InstalledEntries { get; }
|
||||
|
||||
public ReadOnlyObservableCollection<IGrouping<InstalledTabItemViewModel, string>> EntryGroups { get; }
|
||||
|
||||
private Func<InstalledEntry, bool> CreatePredicate(string? text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
|
||||
@@ -3,41 +3,44 @@
|
||||
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.Library.Tabs"
|
||||
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
xmlns:avalonia1="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Library.Tabs.SubmissionsTabItemView"
|
||||
x:DataType="tabs:SubmissionsTabItemViewModel">
|
||||
<UserControl.Resources>
|
||||
<UserControl.Resources>
|
||||
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
|
||||
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
||||
</UserControl.Resources>
|
||||
<Button MinHeight="80"
|
||||
MaxHeight="110"
|
||||
Padding="12 6"
|
||||
Margin="0 0 0 5"
|
||||
<Button MinHeight="110"
|
||||
MaxHeight="140"
|
||||
Padding="12"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Command="{CompiledBinding NavigateToEntry}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
Command="{CompiledBinding NavigateToEntry}"
|
||||
IsVisible="{CompiledBinding Entry, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="*, Auto">
|
||||
<!-- Icon -->
|
||||
<Border Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
CornerRadius="6"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 10 0"
|
||||
Width="50"
|
||||
Height="50"
|
||||
Width="80"
|
||||
Height="80"
|
||||
ClipToBounds="True">
|
||||
<Image Stretch="UniformToFill" asyncImageLoader:ImageLoader.Source="{CompiledBinding Entry.Id, Converter={StaticResource EntryIconUriConverter}, Mode=OneWay}" />
|
||||
<Image Stretch="UniformToFill" il:ImageLoader.Source="{CompiledBinding Entry.Id, Converter={StaticResource EntryIconUriConverter}, Mode=OneWay}" />
|
||||
</Border>
|
||||
|
||||
<!-- Body -->
|
||||
<Grid Grid.Column="1" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Row="0"
|
||||
Classes="h5 no-margin"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
|
||||
<Grid Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Row="0" Margin="0 0 0 5" TextTrimming="CharacterEllipsis">
|
||||
<Run Classes="h5" Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
|
||||
<Run Classes="subtitle">by you</Run>
|
||||
<Run Classes="subtitle" Text="{CompiledBinding Emoji}" />
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Row="1"
|
||||
Classes="subtitle"
|
||||
TextWrapping="Wrap"
|
||||
@@ -54,7 +57,7 @@
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia1:MaterialIcon Kind="{CompiledBinding Icon}" Margin="0 0 3 0"></avalonia1:MaterialIcon>
|
||||
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Margin="0 0 3 0"></avalonia:MaterialIcon>
|
||||
<TextBlock Text="{CompiledBinding Name}" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
@@ -63,10 +66,10 @@
|
||||
</Grid>
|
||||
|
||||
<!-- Info -->
|
||||
<StackPanel Grid.Column="2" Margin="0 0 4 0">
|
||||
<StackPanel Grid.Column="2" Grid.Row="0" Margin="0 0 4 0" HorizontalAlignment="Right">
|
||||
<TextBlock TextAlignment="Right" Text="{CompiledBinding Entry.CreatedAt, FallbackValue=01-01-1337, Converter={StaticResource DateTimeConverter}}" />
|
||||
<TextBlock TextAlignment="Right">
|
||||
<avalonia1:MaterialIcon Kind="Downloads" />
|
||||
<avalonia:MaterialIcon Kind="Downloads" />
|
||||
<Run Classes="h5" Text="{CompiledBinding Entry.Downloads, FallbackValue=0}" />
|
||||
<Run>downloads</Run>
|
||||
</TextBlock>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -10,6 +11,7 @@ namespace Artemis.UI.Screens.Workshop.Library.Tabs;
|
||||
|
||||
public class SubmissionsTabItemViewModel : ViewModelBase
|
||||
{
|
||||
private static readonly string[] Emojis = ["❤️", "🧡", "💛", "💚", "💙", "💜", "🤍", "💔", "❣️", "💕", "💞", "💓", "💗", "💖", "💘", "💝", "😍", "🥰"];
|
||||
private readonly IRouter _router;
|
||||
|
||||
public SubmissionsTabItemViewModel(IGetSubmittedEntries_SubmittedEntries entry, IRouter router)
|
||||
@@ -18,9 +20,11 @@ public class SubmissionsTabItemViewModel : ViewModelBase
|
||||
Entry = entry;
|
||||
|
||||
NavigateToEntry = ReactiveCommand.CreateFromTask(ExecuteNavigateToEntry);
|
||||
Emoji = Emojis[Random.Shared.Next(0, Emojis.Length)];
|
||||
}
|
||||
|
||||
public IGetSubmittedEntries_SubmittedEntries Entry { get; }
|
||||
public string Emoji { get; }
|
||||
public ReactiveCommand<Unit, Unit> NavigateToEntry { get; }
|
||||
|
||||
private async Task ExecuteNavigateToEntry(CancellationToken cancellationToken)
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="650"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Library.Tabs.SubmissionsTabView"
|
||||
x:DataType="tabs:SubmissionsTabViewModel">
|
||||
|
||||
<UserControl.Styles>
|
||||
<Styles>
|
||||
<Style Selector="StackPanel.empty-state > TextBlock">
|
||||
@@ -26,8 +25,16 @@
|
||||
<Button HorizontalAlignment="Center" Command="{CompiledBinding Login}">Log in</Button>
|
||||
</StackPanel>
|
||||
|
||||
<Panel IsVisible="{CompiledBinding IsLoggedIn^}">
|
||||
<StackPanel IsVisible="{CompiledBinding !Entries.Count}" Margin="0 50 0 0" Classes="empty-state">
|
||||
<Grid IsVisible="{CompiledBinding IsLoggedIn^}" RowDefinitions="Auto,*">
|
||||
<Grid Grid.Row="0" Grid.Column="0" MaxWidth="1000" Margin="0 22 0 10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="165" MaxWidth="400" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox Classes="search-box" Text="{CompiledBinding SearchEntryInput}" Watermark="Search submissions" Margin="0 0 10 0" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" IsVisible="{CompiledBinding Empty}" Margin="0 50 0 0" Classes="empty-state">
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">Oh boy, it's empty here 🤔</TextBlock>
|
||||
<TextBlock>
|
||||
<Run>Any entries you submit to the workshop you can later manage here</Run>
|
||||
@@ -36,25 +43,35 @@
|
||||
<Button HorizontalAlignment="Center" Command="{CompiledBinding AddSubmission}">Submit new entry</Button>
|
||||
</StackPanel>
|
||||
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Button Grid.Row="0" Margin="0 0 0 15" HorizontalAlignment="Right" Command="{CompiledBinding AddSubmission}">Submit new entry</Button>
|
||||
<ScrollViewer Grid.Row="1" IsVisible="{CompiledBinding Entries.Count}">
|
||||
<ItemsControl ItemsSource="{CompiledBinding Entries}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{CompiledBinding}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
</Panel>
|
||||
<ScrollViewer Grid.Row="1" Grid.Column="0" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" VerticalAlignment="Top">
|
||||
<ItemsControl ItemsSource="{CompiledBinding EntryGroups}" MaxWidth="1000">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="10"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}" Text="{Binding Key}" Margin="0 0 0 5"/>
|
||||
<ItemsControl ItemsSource="{Binding Items}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{Binding}" Margin="0 0 0 8" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Panel>
|
||||
|
||||
</UserControl>
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Extensions;
|
||||
@@ -11,6 +12,9 @@ using Artemis.UI.Shared.Services;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using DynamicData;
|
||||
using DynamicData.Aggregation;
|
||||
using DynamicData.Binding;
|
||||
using Humanizer;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
using StrawberryShake;
|
||||
@@ -22,28 +26,37 @@ public partial class SubmissionsTabViewModel : RoutableScreen
|
||||
private readonly IWorkshopClient _client;
|
||||
private readonly SourceCache<IGetSubmittedEntries_SubmittedEntries, long> _entries;
|
||||
private readonly IWindowService _windowService;
|
||||
private readonly ObservableAsPropertyHelper<bool> _empty;
|
||||
|
||||
[Notify] private bool _isLoading = true;
|
||||
[Notify] private bool _workshopReachable;
|
||||
|
||||
[Notify] private string? _searchEntryInput;
|
||||
|
||||
public SubmissionsTabViewModel(IWorkshopClient client,
|
||||
IAuthenticationService authenticationService,
|
||||
IWindowService windowService,
|
||||
IWorkshopService workshopService,
|
||||
Func<IGetSubmittedEntries_SubmittedEntries, SubmissionsTabItemViewModel> getSubmissionsTabItemViewModel)
|
||||
{
|
||||
IObservable<Func<IGetSubmittedEntries_SubmittedEntries, bool>> searchFilter = this.WhenAnyValue(vm => vm.SearchEntryInput).Throttle(TimeSpan.FromMilliseconds(100)).Select(CreatePredicate);
|
||||
|
||||
_client = client;
|
||||
_windowService = windowService;
|
||||
_entries = new SourceCache<IGetSubmittedEntries_SubmittedEntries, long>(e => e.Id);
|
||||
_entries.Connect()
|
||||
.Filter(searchFilter)
|
||||
.Sort(SortExpressionComparer<IGetSubmittedEntries_SubmittedEntries>.Descending(p => p.CreatedAt))
|
||||
.Transform(getSubmissionsTabItemViewModel)
|
||||
.Bind(out ReadOnlyObservableCollection<SubmissionsTabItemViewModel> entries)
|
||||
.GroupWithImmutableState(vm => vm.Entry.EntryType.Humanize(LetterCasing.Title).Pluralize())
|
||||
.Bind(out ReadOnlyObservableCollection<IGrouping<SubmissionsTabItemViewModel, long, string>> entries)
|
||||
.Subscribe();
|
||||
|
||||
_empty = _entries.Connect().Count().Select(c => c == 0).ToProperty(this, vm => vm.Empty);
|
||||
|
||||
AddSubmission = ReactiveCommand.CreateFromTask(ExecuteAddSubmission, this.WhenAnyValue(vm => vm.WorkshopReachable));
|
||||
Login = ReactiveCommand.CreateFromTask(ExecuteLogin, this.WhenAnyValue(vm => vm.WorkshopReachable));
|
||||
|
||||
IsLoggedIn = authenticationService.IsLoggedIn;
|
||||
Entries = entries;
|
||||
EntryGroups = entries;
|
||||
|
||||
this.WhenActivatedAsync(async d =>
|
||||
{
|
||||
@@ -53,10 +66,11 @@ public partial class SubmissionsTabViewModel : RoutableScreen
|
||||
});
|
||||
}
|
||||
|
||||
public bool Empty => _empty.Value;
|
||||
public ReactiveCommand<Unit, Unit> Login { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddSubmission { get; }
|
||||
public IObservable<bool> IsLoggedIn { get; }
|
||||
public ReadOnlyObservableCollection<SubmissionsTabItemViewModel> Entries { get; }
|
||||
public ReadOnlyObservableCollection<IGrouping<SubmissionsTabItemViewModel, long, string>> EntryGroups { get; }
|
||||
|
||||
private async Task ExecuteLogin(CancellationToken ct)
|
||||
{
|
||||
@@ -91,4 +105,12 @@ public partial class SubmissionsTabViewModel : RoutableScreen
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private Func<IGetSubmittedEntries_SubmittedEntries, bool> CreatePredicate(string? text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return _ => true;
|
||||
|
||||
return data => data.Name.Contains(text, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@
|
||||
<ItemsControl ItemsSource="{CompiledBinding Dependants}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="5"></StackPanel>
|
||||
<StackPanel Spacing="8"></StackPanel>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Plugins.PluginDetailsView"
|
||||
x:DataType="plugins:PluginDetailsViewModel">
|
||||
<Grid ColumnDefinitions="300,*, 300" RowDefinitions="Auto,*">
|
||||
<Grid ColumnDefinitions="300,*, 300" RowDefinitions="Auto,*" MaxWidth="1600" HorizontalAlignment="Stretch">
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" Spacing="10">
|
||||
<Border Classes="card" VerticalAlignment="Top">
|
||||
<ContentControl Content="{CompiledBinding EntryInfoViewModel}" />
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<ItemsControl ItemsSource="{CompiledBinding Dependencies}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Spacing="5"></StackPanel>
|
||||
<StackPanel Spacing="8"></StackPanel>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Profile.ProfileDetailsView"
|
||||
x:DataType="profile:ProfileDetailsViewModel">
|
||||
<Grid ColumnDefinitions="300,*, 300" RowDefinitions="Auto,*">
|
||||
<Grid ColumnDefinitions="300,*, 300" RowDefinitions="Auto,*" MaxWidth="1600" HorizontalAlignment="Stretch">
|
||||
<StackPanel Grid.Row="1" Grid.Column="0" Spacing="10">
|
||||
<Border Classes="card" VerticalAlignment="Top">
|
||||
<ContentControl Content="{CompiledBinding EntryInfoViewModel}" />
|
||||
|
||||
Reference in new issue
Block a user