1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 21:38:38 +00:00

Workshop - Fix library virtualization

Workshop - Tweak screenshots scaling
This commit is contained in:
Robert 2024-01-14 20:38:17 +01:00
parent 28edabae89
commit 8b4b5d8810
13 changed files with 92 additions and 37 deletions

View File

@ -14,7 +14,7 @@
<TextBlock Text="Current layout" /> <TextBlock Text="Current layout" />
<TextBlock Classes="subtitle" FontSize="12" Text="Loading the layout from a workshop entry" TextWrapping="Wrap" /> <TextBlock Classes="subtitle" FontSize="12" Text="Loading the layout from a workshop entry" TextWrapping="Wrap" />
</StackPanel> </StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center"> <StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Spacing="5">
<StackPanel.Styles> <StackPanel.Styles>
<Style Selector="ComboBox.layoutProvider /template/ ContentControl#ContentPresenter"> <Style Selector="ComboBox.layoutProvider /template/ ContentControl#ContentPresenter">
<Setter Property="ContentTemplate"> <Setter Property="ContentTemplate">
@ -40,6 +40,7 @@
</DataTemplate> </DataTemplate>
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<Button HorizontalAlignment="Right" Click="Button_OnClick">Browse workshop layouts</Button>
</StackPanel> </StackPanel>
</Grid> </Grid>
</StackPanel> </StackPanel>

View File

@ -1,4 +1,6 @@
using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Device.Layout.LayoutProviders; namespace Artemis.UI.Screens.Device.Layout.LayoutProviders;
@ -9,4 +11,10 @@ public partial class WorkshopLayoutView : ReactiveUserControl<WorkshopLayoutView
{ {
InitializeComponent(); InitializeComponent();
} }
private async void Button_OnClick(object? sender, RoutedEventArgs e)
{
if (ViewModel != null && await ViewModel.BrowseLayouts())
(VisualRoot as Window)?.Close();
}
} }

View File

@ -2,10 +2,13 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Providers; using Artemis.Core.Providers;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop; using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Providers; using Artemis.WebClient.Workshop.Providers;
using Artemis.WebClient.Workshop.Services; using Artemis.WebClient.Workshop.Services;
@ -19,11 +22,16 @@ public partial class WorkshopLayoutViewModel : ActivatableViewModelBase, ILayout
[Notify] private InstalledEntry? _selectedEntry; [Notify] private InstalledEntry? _selectedEntry;
private readonly WorkshopLayoutProvider _layoutProvider; private readonly WorkshopLayoutProvider _layoutProvider;
private readonly IDeviceService _deviceService; private readonly IDeviceService _deviceService;
private readonly IWindowService _windowService;
private readonly IRouter _router;
public WorkshopLayoutViewModel(WorkshopLayoutProvider layoutProvider, IWorkshopService workshopService, IDeviceService deviceService) public WorkshopLayoutViewModel(WorkshopLayoutProvider layoutProvider, IWorkshopService workshopService, IDeviceService deviceService, IWindowService windowService, IRouter router)
{ {
_layoutProvider = layoutProvider; _layoutProvider = layoutProvider;
_deviceService = deviceService; _deviceService = deviceService;
_windowService = windowService;
_router = router;
Entries = new ObservableCollection<InstalledEntry>(workshopService.GetInstalledEntries().Where(e => e.EntryType == EntryType.Layout)); Entries = new ObservableCollection<InstalledEntry>(workshopService.GetInstalledEntries().Where(e => e.EntryType == EntryType.Layout));
this.WhenAnyValue(vm => vm.SelectedEntry).Subscribe(ApplyEntry); this.WhenAnyValue(vm => vm.SelectedEntry).Subscribe(ApplyEntry);
@ -50,6 +58,15 @@ public partial class WorkshopLayoutViewModel : ActivatableViewModelBase, ILayout
Save(); Save();
} }
public async Task<bool> BrowseLayouts()
{
if (!await _windowService.ShowConfirmContentDialog("Open workshop", "Do you want to close this window and view the workshop?"))
return false;
await _router.Navigate("workshop/entries/layouts/1");
return true;
}
private void ApplyEntry(InstalledEntry? entry) private void ApplyEntry(InstalledEntry? entry)
{ {
if (entry == null || Device.LayoutSelection.Parameter == entry.EntryId.ToString()) if (entry == null || Device.LayoutSelection.Parameter == entry.EntryId.ToString())

View File

@ -8,13 +8,9 @@
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryImageView" x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryImageView"
x:DataType="details:EntryImageViewModel"> x:DataType="details:EntryImageViewModel">
<Border Classes="card" Padding="0"> <Border Classes="card" Padding="0">
<Grid RowDefinitions="230,*"> <Grid RowDefinitions="Auto,*">
<Border Grid.Row="0" ClipToBounds="True" CornerRadius="4 4 0 0" Padding="0"> <Border Grid.Row="0" ClipToBounds="True" CornerRadius="4 4 0 0" Padding="0">
<Rectangle RenderOptions.BitmapInterpolationMode="HighQuality"> <Image asyncImageLoader:ImageLoader.Source="{CompiledBinding ThumbnailUrl}" Stretch="Uniform" HorizontalAlignment="Stretch" MaxHeight="250" />
<Rectangle.Fill>
<ImageBrush asyncImageLoader:ImageBrushLoader.Source="{CompiledBinding ThumbnailUrl}" Stretch="UniformToFill" />
</Rectangle.Fill>
</Rectangle>
</Border> </Border>
<Border Grid.Row="1" ClipToBounds="True" CornerRadius="0 0 4 4" Background="{DynamicResource ControlFillColorDefaultBrush}"> <Border Grid.Row="1" ClipToBounds="True" CornerRadius="0 0 4 4" Background="{DynamicResource ControlFillColorDefaultBrush}">
<StackPanel Margin="16"> <StackPanel Margin="16">

View File

@ -8,5 +8,5 @@
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryImagesDialogView" x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryImagesDialogView"
x:DataType="details:EntryImagesDialogViewModel" x:DataType="details:EntryImagesDialogViewModel"
Margin="-25 -63 -25 -25"> Margin="-25 -63 -25 -25">
<Image asyncImageLoader:ImageLoader.Source="{CompiledBinding CurrentImage.Url}" Stretch="None"/> <Image asyncImageLoader:ImageLoader.Source="{CompiledBinding CurrentImage.Url}" Stretch="Uniform"/>
</UserControl> </UserControl>

View File

@ -8,6 +8,7 @@ using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Utilities; using Artemis.UI.Shared.Utilities;
using Artemis.WebClient.Workshop; using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers; using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
using Artemis.WebClient.Workshop.Services;
using Humanizer; using Humanizer;
using ReactiveUI; using ReactiveUI;
@ -32,6 +33,8 @@ public class EntryReleasesViewModel : ViewModelBase
public IGetEntryById_Entry Entry { get; } public IGetEntryById_Entry Entry { get; }
public ReactiveCommand<Unit, Unit> DownloadLatestRelease { get; } public ReactiveCommand<Unit, Unit> DownloadLatestRelease { get; }
public Func<InstalledEntry, Task>? OnInstallationFinished { get; set; }
private async Task ExecuteDownloadLatestRelease(CancellationToken cancellationToken) private async Task ExecuteDownloadLatestRelease(CancellationToken cancellationToken)
{ {
if (Entry.LatestRelease == null) if (Entry.LatestRelease == null)
@ -46,8 +49,12 @@ public class EntryReleasesViewModel : ViewModelBase
IEntryInstallationHandler installationHandler = _factory.CreateHandler(Entry.EntryType); IEntryInstallationHandler installationHandler = _factory.CreateHandler(Entry.EntryType);
EntryInstallResult result = await installationHandler.InstallAsync(Entry, Entry.LatestRelease, new Progress<StreamProgress>(), cancellationToken); EntryInstallResult result = await installationHandler.InstallAsync(Entry, Entry.LatestRelease, new Progress<StreamProgress>(), cancellationToken);
if (result.IsSuccess) if (result.IsSuccess && result.Entry != null)
{
if (OnInstallationFinished != null)
await OnInstallationFinished(result.Entry);
_notificationService.CreateNotification().WithTitle($"{Entry.EntryType.Humanize(LetterCasing.Sentence)} installed").WithSeverity(NotificationSeverity.Success).Show(); _notificationService.CreateNotification().WithTitle($"{Entry.EntryType.Humanize(LetterCasing.Sentence)} installed").WithSeverity(NotificationSeverity.Success).Show();
}
else else
{ {
_notificationService.CreateNotification() _notificationService.CreateNotification()

View File

@ -8,7 +8,7 @@
x:Class="Artemis.UI.Screens.Workshop.Layout.LayoutDetailsView" x:Class="Artemis.UI.Screens.Workshop.Layout.LayoutDetailsView"
x:DataType="layout:LayoutDetailsViewModel"> x:DataType="layout:LayoutDetailsViewModel">
<Grid ColumnDefinitions="300,*, 300" RowDefinitions="Auto,*"> <Grid ColumnDefinitions="300,*, 300" RowDefinitions="Auto,*">
<StackPanel Grid.Row="1" Grid.Column="0" Margin="0 0 10 0" Spacing="10"> <StackPanel Grid.Row="1" Grid.Column="0" Spacing="10">
<Border Classes="card" VerticalAlignment="Top"> <Border Classes="card" VerticalAlignment="Top">
<ContentControl Content="{CompiledBinding EntryInfoViewModel}" /> <ContentControl Content="{CompiledBinding EntryInfoViewModel}" />
</Border> </Border>
@ -17,7 +17,7 @@
</Border> </Border>
</StackPanel> </StackPanel>
<Border Classes="card" Grid.Row="1" Grid.Column="1"> <Border Classes="card" Grid.Row="1" Grid.Column="1" Margin="10 0">
<mdxaml:MarkdownScrollViewer Markdown="{CompiledBinding Entry.Description}" MarkdownStyleName="FluentAvalonia"> <mdxaml:MarkdownScrollViewer Markdown="{CompiledBinding Entry.Description}" MarkdownStyleName="FluentAvalonia">
<mdxaml:MarkdownScrollViewer.Styles> <mdxaml:MarkdownScrollViewer.Styles>
<StyleInclude Source="/Styles/Markdown.axaml" /> <StyleInclude Source="/Styles/Markdown.axaml" />
@ -25,8 +25,6 @@
</mdxaml:MarkdownScrollViewer> </mdxaml:MarkdownScrollViewer>
</Border> </Border>
<StackPanel Grid.Row="1" Grid.Column="2" IsVisible="{CompiledBinding Entry.Images.Count}"> <ContentControl Grid.Row="1" Grid.Column="2" IsVisible="{CompiledBinding Entry.Images.Count}" Content="{CompiledBinding EntryImagesViewModel}" />
<ContentControl Content="{CompiledBinding EntryImagesViewModel}" />
</StackPanel>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -1,10 +1,12 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.Services;
using Artemis.UI.Screens.Workshop.Entries.Details; using Artemis.UI.Screens.Workshop.Entries.Details;
using Artemis.UI.Screens.Workshop.Parameters; using Artemis.UI.Screens.Workshop.Parameters;
using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Workshop; using Artemis.WebClient.Workshop;
using Artemis.WebClient.Workshop.Services;
using PropertyChanged.SourceGenerator; using PropertyChanged.SourceGenerator;
using StrawberryShake; using StrawberryShake;
@ -13,6 +15,7 @@ namespace Artemis.UI.Screens.Workshop.Layout;
public partial class LayoutDetailsViewModel : RoutableScreen<WorkshopDetailParameters> public partial class LayoutDetailsViewModel : RoutableScreen<WorkshopDetailParameters>
{ {
private readonly IWorkshopClient _client; private readonly IWorkshopClient _client;
private readonly IDeviceService _deviceService;
private readonly Func<IGetEntryById_Entry, EntryInfoViewModel> _getEntryInfoViewModel; private readonly Func<IGetEntryById_Entry, EntryInfoViewModel> _getEntryInfoViewModel;
private readonly Func<IGetEntryById_Entry, EntryReleasesViewModel> _getEntryReleasesViewModel; private readonly Func<IGetEntryById_Entry, EntryReleasesViewModel> _getEntryReleasesViewModel;
private readonly Func<IGetEntryById_Entry, EntryImagesViewModel> _getEntryImagesViewModel; private readonly Func<IGetEntryById_Entry, EntryImagesViewModel> _getEntryImagesViewModel;
@ -22,11 +25,13 @@ public partial class LayoutDetailsViewModel : RoutableScreen<WorkshopDetailParam
[Notify] private EntryImagesViewModel? _entryImagesViewModel; [Notify] private EntryImagesViewModel? _entryImagesViewModel;
public LayoutDetailsViewModel(IWorkshopClient client, public LayoutDetailsViewModel(IWorkshopClient client,
IDeviceService deviceService,
Func<IGetEntryById_Entry, EntryInfoViewModel> getEntryInfoViewModel, Func<IGetEntryById_Entry, EntryInfoViewModel> getEntryInfoViewModel,
Func<IGetEntryById_Entry, EntryReleasesViewModel> getEntryReleasesViewModel, Func<IGetEntryById_Entry, EntryReleasesViewModel> getEntryReleasesViewModel,
Func<IGetEntryById_Entry, EntryImagesViewModel> getEntryImagesViewModel) Func<IGetEntryById_Entry, EntryImagesViewModel> getEntryImagesViewModel)
{ {
_client = client; _client = client;
_deviceService = deviceService;
_getEntryInfoViewModel = getEntryInfoViewModel; _getEntryInfoViewModel = getEntryInfoViewModel;
_getEntryReleasesViewModel = getEntryReleasesViewModel; _getEntryReleasesViewModel = getEntryReleasesViewModel;
_getEntryImagesViewModel = getEntryImagesViewModel; _getEntryImagesViewModel = getEntryImagesViewModel;
@ -54,6 +59,17 @@ public partial class LayoutDetailsViewModel : RoutableScreen<WorkshopDetailParam
EntryInfoViewModel = _getEntryInfoViewModel(Entry); EntryInfoViewModel = _getEntryInfoViewModel(Entry);
EntryReleasesViewModel = _getEntryReleasesViewModel(Entry); EntryReleasesViewModel = _getEntryReleasesViewModel(Entry);
EntryImagesViewModel = _getEntryImagesViewModel(Entry); EntryImagesViewModel = _getEntryImagesViewModel(Entry);
EntryReleasesViewModel.OnInstallationFinished = OnInstallationFinished;
} }
} }
private Task OnInstallationFinished(InstalledEntry installedEntry)
{
// Find compatible devices
// If any are found, offer to apply
return Task.CompletedTask;
}
} }

View File

@ -26,13 +26,18 @@
</StackPanel> </StackPanel>
<ScrollViewer IsVisible="{CompiledBinding !Empty}"> <ScrollViewer IsVisible="{CompiledBinding !Empty}">
<ItemsRepeater ItemsSource="{CompiledBinding InstalledEntries}"> <ItemsControl ItemsSource="{CompiledBinding InstalledEntries}">
<ItemsRepeater.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<ContentControl Content="{CompiledBinding}"/> <ContentControl Content="{CompiledBinding}"/>
</DataTemplate> </DataTemplate>
</ItemsRepeater.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsRepeater> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer> </ScrollViewer>
</Panel> </Panel>

View File

@ -37,13 +37,18 @@
</StackPanel> </StackPanel>
<ScrollViewer IsVisible="{CompiledBinding Entries.Count}"> <ScrollViewer IsVisible="{CompiledBinding Entries.Count}">
<ItemsRepeater ItemsSource="{CompiledBinding Entries}"> <ItemsControl ItemsSource="{CompiledBinding Entries}">
<ItemsRepeater.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<ContentControl Content="{CompiledBinding}"/> <ContentControl Content="{CompiledBinding}"/>
</DataTemplate> </DataTemplate>
</ItemsRepeater.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsRepeater> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer> </ScrollViewer>
</Panel> </Panel>
</Panel> </Panel>

View File

@ -1,10 +1,12 @@
namespace Artemis.WebClient.Workshop.Handlers.InstallationHandlers; using Artemis.WebClient.Workshop.Services;
namespace Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
public class EntryInstallResult public class EntryInstallResult
{ {
public bool IsSuccess { get; set; } public bool IsSuccess { get; set; }
public string? Message { get; set; } public string? Message { get; set; }
public object? Result { get; set; } public InstalledEntry? Entry { get; set; }
public static EntryInstallResult FromFailure(string? message) public static EntryInstallResult FromFailure(string? message)
{ {
@ -15,12 +17,12 @@ public class EntryInstallResult
}; };
} }
public static EntryInstallResult FromSuccess(object installationResult) public static EntryInstallResult FromSuccess(InstalledEntry installedEntry)
{ {
return new EntryInstallResult return new EntryInstallResult
{ {
IsSuccess = true, IsSuccess = true,
Result = installationResult Entry = installedEntry
}; };
} }
} }

View File

@ -41,28 +41,28 @@ public class LayoutEntryInstallationHandler : IEntryInstallationHandler
// Ensure there is an installed entry // Ensure there is an installed entry
InstalledEntry installedEntry = _workshopService.GetInstalledEntry(entry) ?? new InstalledEntry(entry, release); InstalledEntry installedEntry = _workshopService.GetInstalledEntry(entry) ?? new InstalledEntry(entry, release);
DirectoryInfo entryDirectory = installedEntry.GetReleaseDirectory(release); DirectoryInfo releaseDirectory = installedEntry.GetReleaseDirectory(release);
// If the folder already exists, remove it so that if the layout now contains less files, old things dont stick around // If the folder already exists, remove it so that if the layout now contains less files, old things dont stick around
if (entryDirectory.Exists) if (releaseDirectory.Exists)
entryDirectory.Delete(true); releaseDirectory.Delete(true);
entryDirectory.Create(); releaseDirectory.Create();
// Extract the archive, we could go through the hoops of keeping track of progress but this should be so quick it doesn't matter // Extract the archive, we could go through the hoops of keeping track of progress but this should be so quick it doesn't matter
stream.Seek(0, SeekOrigin.Begin); stream.Seek(0, SeekOrigin.Begin);
using ZipArchive archive = new(stream); using ZipArchive archive = new(stream);
archive.ExtractToDirectory(entryDirectory.FullName); archive.ExtractToDirectory(releaseDirectory.FullName);
ArtemisLayout layout = new(Path.Combine(entryDirectory.FullName, "layout.xml")); ArtemisLayout layout = new(Path.Combine(releaseDirectory.FullName, "layout.xml"));
if (layout.IsValid) if (layout.IsValid)
{ {
installedEntry.ApplyRelease(release); installedEntry.ApplyRelease(release);
_workshopService.SaveInstalledEntry(installedEntry); _workshopService.SaveInstalledEntry(installedEntry);
return EntryInstallResult.FromSuccess(layout); return EntryInstallResult.FromSuccess(installedEntry);
} }
// If the layout ended up being invalid yoink it out again, shoooo // If the layout ended up being invalid yoink it out again, shoooo
entryDirectory.Delete(true); releaseDirectory.Delete(true);
_workshopService.RemoveInstalledEntry(installedEntry); _workshopService.RemoveInstalledEntry(installedEntry);
return EntryInstallResult.FromFailure("Layout failed to load because it is invalid"); return EntryInstallResult.FromFailure("Layout failed to load because it is invalid");
} }

View File

@ -46,7 +46,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
// Update the release and return the profile configuration // Update the release and return the profile configuration
UpdateRelease(installedEntry, release); UpdateRelease(installedEntry, release);
return EntryInstallResult.FromSuccess(overwritten); return EntryInstallResult.FromSuccess(installedEntry);
} }
} }
@ -60,7 +60,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
// Update the release and return the profile configuration // Update the release and return the profile configuration
UpdateRelease(installedEntry, release); UpdateRelease(installedEntry, release);
return EntryInstallResult.FromSuccess(imported); return EntryInstallResult.FromSuccess(installedEntry);
} }
public async Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken) public async Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken)