mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Images WIP
This commit is contained in:
parent
6b4e84c95a
commit
0667d58ed8
@ -58,4 +58,19 @@
|
|||||||
<HintPath>..\..\..\RGB.NET\bin\net7.0\RGB.NET.Layout.dll</HintPath>
|
<HintPath>..\..\..\RGB.NET\bin\net7.0\RGB.NET.Layout.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Screens\Workshop\Entries\List\EntryListInputView.axaml.cs">
|
||||||
|
<DependentUpon>EntryListInputView.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Screens\Workshop\Entries\List\EntryListItemView.axaml.cs">
|
||||||
|
<DependentUpon>EntryListItemView.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Screens\Workshop\Entries\Details\EntrySpecificationsView.axaml.cs">
|
||||||
|
<DependentUpon>EntrySpecificationsView.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@ -6,6 +6,7 @@ using Artemis.UI.Screens.Debugger.Logs;
|
|||||||
using Artemis.UI.Screens.Debugger.Performance;
|
using Artemis.UI.Screens.Debugger.Performance;
|
||||||
using Artemis.UI.Screens.Debugger.Render;
|
using Artemis.UI.Screens.Debugger.Render;
|
||||||
using Artemis.UI.Screens.Debugger.Routing;
|
using Artemis.UI.Screens.Debugger.Routing;
|
||||||
|
using Artemis.UI.Screens.Debugger.Workshop;
|
||||||
using Artemis.UI.Services.Interfaces;
|
using Artemis.UI.Services.Interfaces;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
@ -17,9 +18,9 @@ public partial class DebugViewModel : ActivatableViewModelBase, IScreen
|
|||||||
{
|
{
|
||||||
[Notify] private ViewModelBase _selectedItem;
|
[Notify] private ViewModelBase _selectedItem;
|
||||||
|
|
||||||
public DebugViewModel(IDebugService debugService, RenderDebugViewModel render, DataModelDebugViewModel dataModel, PerformanceDebugViewModel performance, RoutingDebugViewModel routing, LogsDebugViewModel logs)
|
public DebugViewModel(IDebugService debugService, RenderDebugViewModel render, DataModelDebugViewModel dataModel, PerformanceDebugViewModel performance, RoutingDebugViewModel routing, WorkshopDebugViewModel workshop, LogsDebugViewModel logs)
|
||||||
{
|
{
|
||||||
Items = new ObservableCollection<ViewModelBase> {render, dataModel, performance, routing, logs};
|
Items = new ObservableCollection<ViewModelBase> {render, dataModel, performance, routing, workshop, logs};
|
||||||
_selectedItem = render;
|
_selectedItem = render;
|
||||||
|
|
||||||
this.WhenActivated(d => Disposable.Create(debugService.ClearDebugger).DisposeWith(d));
|
this.WhenActivated(d => Disposable.Create(debugService.ClearDebugger).DisposeWith(d));
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
<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:workshop="clr-namespace:Artemis.UI.Screens.Debugger.Workshop"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.Debugger.Workshop.WorkshopDebugView"
|
||||||
|
x:DataType="workshop:WorkshopDebugViewModel">
|
||||||
|
<ScrollViewer Classes="with-padding">
|
||||||
|
<StackPanel>
|
||||||
|
<Label>Workshop Status</Label>
|
||||||
|
<Border Classes="card-condensed">
|
||||||
|
<SelectableTextBlock Text="{CompiledBinding WorkshopStatus}" FontFamily="{StaticResource RobotoMono}" FontSize="13" />
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Label Margin="0 10 0 0">Auth token (DO NOT SHARE)</Label>
|
||||||
|
<Border Classes="card-condensed">
|
||||||
|
<SelectableTextBlock Text="{CompiledBinding Token}" FontFamily="{StaticResource RobotoMono}" FontSize="13" TextWrapping="Wrap" />
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Label Margin="0 10 0 0">Email verified</Label>
|
||||||
|
<Border Classes="card-condensed">
|
||||||
|
<SelectableTextBlock Text="{CompiledBinding EmailVerified}" FontFamily="{StaticResource RobotoMono}" FontSize="13" />
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Label Margin="0 10 0 0">Claims</Label>
|
||||||
|
<Border Classes="card-condensed">
|
||||||
|
<SelectableTextBlock Text="{CompiledBinding Claims}" FontFamily="{StaticResource RobotoMono}" FontSize="13" />
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Debugger.Workshop;
|
||||||
|
|
||||||
|
public partial class WorkshopDebugView : ReactiveUserControl<WorkshopDebugViewModel>
|
||||||
|
{
|
||||||
|
public WorkshopDebugView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using Artemis.UI.Extensions;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.WebClient.Workshop.Services;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Debugger.Workshop;
|
||||||
|
|
||||||
|
public partial class WorkshopDebugViewModel : ActivatableViewModelBase
|
||||||
|
{
|
||||||
|
|
||||||
|
[Notify] private string? _token;
|
||||||
|
[Notify] private bool _emailVerified;
|
||||||
|
[Notify] private string? _claims;
|
||||||
|
[Notify] private IWorkshopService.WorkshopStatus? _workshopStatus;
|
||||||
|
|
||||||
|
public WorkshopDebugViewModel(IWorkshopService workshopService, IAuthenticationService authenticationService)
|
||||||
|
{
|
||||||
|
DisplayName = "Workshop";
|
||||||
|
|
||||||
|
this.WhenActivatedAsync(async _ =>
|
||||||
|
{
|
||||||
|
Token = await authenticationService.GetBearer();
|
||||||
|
EmailVerified = authenticationService.GetIsEmailVerified();
|
||||||
|
Claims = JsonConvert.SerializeObject(authenticationService.Claims, Formatting.Indented);
|
||||||
|
WorkshopStatus = await workshopService.GetWorkshopStatus(CancellationToken.None);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
<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:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||||
|
xmlns:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryImageView"
|
||||||
|
x:DataType="details:EntryImageViewModel">
|
||||||
|
<Border Classes="card" Padding="0" Width="300" ClipToBounds="True" Margin="0 5 0 0">
|
||||||
|
<Grid RowDefinitions="230,*">
|
||||||
|
<Rectangle Grid.Row="0" Fill="{DynamicResource CheckerboardBrush}" />
|
||||||
|
<Image Grid.Row="0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||||
|
asyncImageLoader:ImageLoader.Source="{CompiledBinding ThumbnailUrl, Mode=OneWay}" />
|
||||||
|
<StackPanel Grid.Row="1" Margin="12">
|
||||||
|
<TextBlock Text="{CompiledBinding Image.Name}" TextTrimming="CharacterEllipsis" />
|
||||||
|
<TextBlock Classes="subtitle" Text="{CompiledBinding Image.Description}" TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||||
|
|
||||||
|
public partial class EntryImageView : UserControl
|
||||||
|
{
|
||||||
|
public EntryImageView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
using Artemis.WebClient.Workshop;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||||
|
|
||||||
|
public class EntryImageViewModel
|
||||||
|
{
|
||||||
|
public EntryImageViewModel(IImage image)
|
||||||
|
{
|
||||||
|
Image = image;
|
||||||
|
Url = $"{WorkshopConstants.WORKSHOP_URL}/images/{image.Id}.png";
|
||||||
|
ThumbnailUrl = $"{WorkshopConstants.WORKSHOP_URL}/images/{image.Id}-thumb.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
public IImage Image { get; }
|
||||||
|
public string Url { get; }
|
||||||
|
public string ThumbnailUrl { get; }
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
<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:system="clr-namespace:System;assembly=System.Runtime"
|
||||||
|
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||||
|
xmlns:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryImagesView"
|
||||||
|
x:DataType="details:EntryImagesViewModel">
|
||||||
|
<ItemsControl ItemsSource="{CompiledBinding Images}" Margin="0 -16 0 0">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<VirtualizingStackPanel />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.DataTemplates>
|
||||||
|
<DataTemplate x:DataType="details:EntryImageViewModel">
|
||||||
|
<Border CornerRadius="6"
|
||||||
|
Margin="0 16 0 0"
|
||||||
|
MaxWidth="300"
|
||||||
|
ClipToBounds="True">
|
||||||
|
<Image Stretch="UniformToFill" asyncImageLoader:ImageLoader.Source="{CompiledBinding ThumbnailUrl, Mode=OneWay}" />
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.DataTemplates>
|
||||||
|
</ItemsControl>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||||
|
|
||||||
|
public partial class EntryImagesView : UserControl
|
||||||
|
{
|
||||||
|
public EntryImagesView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.WebClient.Workshop;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||||
|
|
||||||
|
public class EntryImagesViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
public ObservableCollection<EntryImageViewModel> Images { get; }
|
||||||
|
|
||||||
|
public EntryImagesViewModel(IEntryDetails entryDetails)
|
||||||
|
{
|
||||||
|
Images = new ObservableCollection<EntryImageViewModel>(entryDetails.Images.Select(i => new EntryImageViewModel(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
<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:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
|
||||||
|
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||||
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
|
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryInfoView"
|
||||||
|
x:DataType="details:EntryInfoViewModel">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
|
||||||
|
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<StackPanel>
|
||||||
|
<Panel>
|
||||||
|
<Border CornerRadius="6"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Margin="0 0 10 0"
|
||||||
|
Width="80"
|
||||||
|
Height="80"
|
||||||
|
ClipToBounds="True">
|
||||||
|
<Image Stretch="UniformToFill" il:ImageLoader.Source="{CompiledBinding Entry.Id, Converter={StaticResource EntryIconUriConverter}, Mode=OneWay}" />
|
||||||
|
</Border>
|
||||||
|
<Button Classes="icon-button"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Command="{CompiledBinding CopyShareLink}"
|
||||||
|
ToolTip.Tip="Copy share link">
|
||||||
|
<avalonia:MaterialIcon Kind="ShareVariant" />
|
||||||
|
</Button>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
|
||||||
|
<TextBlock Theme="{StaticResource TitleTextBlockStyle}"
|
||||||
|
MaxLines="3"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
Text="{CompiledBinding Entry.Name, FallbackValue=Title }" />
|
||||||
|
|
||||||
|
<TextBlock Classes="subtitle" TextTrimming="CharacterEllipsis" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
|
||||||
|
|
||||||
|
<TextBlock Margin="0 8" TextWrapping="Wrap" Text="{CompiledBinding Entry.Summary, FallbackValue=Summary}" />
|
||||||
|
|
||||||
|
<!-- Categories -->
|
||||||
|
<ItemsControl ItemsSource="{CompiledBinding Entry.Categories}" Margin="0 0 -8 0">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel Orientation="Horizontal"></WrapPanel>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0 0 8 0">
|
||||||
|
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Margin="0 0 3 0"></avalonia:MaterialIcon>
|
||||||
|
<TextBlock Text="{CompiledBinding Name}" TextTrimming="CharacterEllipsis" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<Border Classes="card-separator"></Border>
|
||||||
|
|
||||||
|
<TextBlock Margin="0 0 0 8">
|
||||||
|
<avalonia:MaterialIcon Kind="Downloads" />
|
||||||
|
<Run Classes="h5" Text="{CompiledBinding Entry.Downloads, FallbackValue=0}" />
|
||||||
|
<Run>downloads</Run>
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<TextBlock Classes="subtitle"
|
||||||
|
ToolTip.Tip="{CompiledBinding Entry.CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
||||||
|
<avalonia:MaterialIcon Kind="Calendar" />
|
||||||
|
<Run>Created</Run>
|
||||||
|
<Run Text="{CompiledBinding Entry.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Classes="subtitle"
|
||||||
|
ToolTip.Tip="{CompiledBinding UpdatedAt, Converter={StaticResource DateTimeConverter}}">
|
||||||
|
<avalonia:MaterialIcon Kind="Update" />
|
||||||
|
<Run>Updated</Run>
|
||||||
|
<Run Text="{CompiledBinding UpdatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||||
|
|
||||||
|
public partial class EntryInfoView : UserControl
|
||||||
|
{
|
||||||
|
public EntryInfoView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.WebClient.Workshop;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||||
|
|
||||||
|
public class EntryInfoViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly INotificationService _notificationService;
|
||||||
|
public IGetEntryById_Entry Entry { get; }
|
||||||
|
public DateTimeOffset? UpdatedAt { get; }
|
||||||
|
|
||||||
|
public EntryInfoViewModel(IGetEntryById_Entry entry, INotificationService notificationService)
|
||||||
|
{
|
||||||
|
_notificationService = notificationService;
|
||||||
|
Entry = entry;
|
||||||
|
UpdatedAt = Entry.LatestRelease?.CreatedAt ?? Entry.CreatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CopyShareLink()
|
||||||
|
{
|
||||||
|
await Shared.UI.Clipboard.SetTextAsync($"{WorkshopConstants.WORKSHOP_URL}/entries/{Entry.Id}/{StringUtilities.UrlFriendly(Entry.Name)}");
|
||||||
|
_notificationService.CreateNotification().WithTitle("Copied share link to clipboard.").Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
<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:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
|
||||||
|
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"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntryReleasesView"
|
||||||
|
x:DataType="details:EntryReleasesViewModel">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
||||||
|
<sharedConverters:BytesToStringConverter x:Key="BytesToStringConverter" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Latest release</TextBlock>
|
||||||
|
<Border Classes="card-separator" />
|
||||||
|
<Button HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
Command="{CompiledBinding DownloadLatestRelease}">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<StackPanel Grid.Column="1" Margin="10 0" VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="{CompiledBinding Entry.LatestRelease.Version, FallbackValue=Version}"></TextBlock>
|
||||||
|
<TextBlock Classes="subtitle">
|
||||||
|
<avalonia:MaterialIcon Kind="BoxOutline" />
|
||||||
|
<Run Text="{CompiledBinding Entry.LatestRelease.DownloadSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay}"></Run>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Classes="subtitle"
|
||||||
|
ToolTip.Tip="{CompiledBinding Entry.LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
||||||
|
<avalonia:MaterialIcon Kind="Calendar" />
|
||||||
|
<Run>Created</Run>
|
||||||
|
<Run Text="{CompiledBinding Entry.LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||||
|
|
||||||
|
public partial class EntryReleasesView : UserControl
|
||||||
|
{
|
||||||
|
public EntryReleasesView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reactive;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.UI.Shared.Services.Builders;
|
||||||
|
using Artemis.UI.Shared.Utilities;
|
||||||
|
using Artemis.WebClient.Workshop;
|
||||||
|
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
||||||
|
using Humanizer;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||||
|
|
||||||
|
public class EntryReleasesViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly EntryInstallationHandlerFactory _factory;
|
||||||
|
private readonly IWindowService _windowService;
|
||||||
|
private readonly INotificationService _notificationService;
|
||||||
|
|
||||||
|
public EntryReleasesViewModel(IGetEntryById_Entry entry, EntryInstallationHandlerFactory factory, IWindowService windowService, INotificationService notificationService)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
_windowService = windowService;
|
||||||
|
_notificationService = notificationService;
|
||||||
|
|
||||||
|
Entry = entry;
|
||||||
|
DownloadLatestRelease = ReactiveCommand.CreateFromTask(ExecuteDownloadLatestRelease);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IGetEntryById_Entry Entry { get; }
|
||||||
|
public ReactiveCommand<Unit, Unit> DownloadLatestRelease { get; }
|
||||||
|
|
||||||
|
private async Task ExecuteDownloadLatestRelease(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (Entry.LatestRelease == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool confirm = await _windowService.ShowConfirmContentDialog(
|
||||||
|
"Install latest release",
|
||||||
|
$"Are you sure you want to download and install version {Entry.LatestRelease.Version} of {Entry.Name}?"
|
||||||
|
);
|
||||||
|
if (!confirm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IEntryInstallationHandler installationHandler = _factory.CreateHandler(Entry.EntryType);
|
||||||
|
EntryInstallResult result = await installationHandler.InstallAsync(Entry, Entry.LatestRelease.Id, new Progress<StreamProgress>(), cancellationToken);
|
||||||
|
if (result.IsSuccess)
|
||||||
|
_notificationService.CreateNotification().WithTitle($"{Entry.EntryType.Humanize(LetterCasing.Sentence)} installed").WithSeverity(NotificationSeverity.Success).Show();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_notificationService.CreateNotification()
|
||||||
|
.WithTitle($"Failed to install {Entry.EntryType.Humanize(LetterCasing.LowerCase)}")
|
||||||
|
.WithMessage(result.Message)
|
||||||
|
.WithSeverity(NotificationSeverity.Error).Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,9 +9,10 @@
|
|||||||
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
|
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
xmlns:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
|
||||||
x:Class="Artemis.UI.Screens.Workshop.Entries.EntrySpecificationsView"
|
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntrySpecificationsView"
|
||||||
x:DataType="entries:EntrySpecificationsViewModel">
|
x:DataType="details:EntrySpecificationsViewModel">
|
||||||
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<StackPanel.Styles>
|
<StackPanel.Styles>
|
||||||
@ -1,5 +1,4 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.UI.Shared.Extensions;
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
@ -8,8 +7,9 @@ using Avalonia.ReactiveUI;
|
|||||||
using AvaloniaEdit.TextMate;
|
using AvaloniaEdit.TextMate;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using TextMateSharp.Grammars;
|
using TextMateSharp.Grammars;
|
||||||
|
using VisualExtensions = Artemis.UI.Shared.Extensions.VisualExtensions;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||||
|
|
||||||
public partial class EntrySpecificationsView : ReactiveUserControl<EntrySpecificationsViewModel>
|
public partial class EntrySpecificationsView : ReactiveUserControl<EntrySpecificationsViewModel>
|
||||||
{
|
{
|
||||||
@ -23,7 +23,7 @@ public partial class EntrySpecificationsView : ReactiveUserControl<EntrySpecific
|
|||||||
|
|
||||||
DescriptionEditor.Options.AllowScrollBelowDocument = false;
|
DescriptionEditor.Options.AllowScrollBelowDocument = false;
|
||||||
RegistryOptions options = new(ThemeName.Dark);
|
RegistryOptions options = new(ThemeName.Dark);
|
||||||
TextMate.Installation? install = DescriptionEditor.InstallTextMate(options);
|
TextMate.Installation? install = TextMate.InstallTextMate(DescriptionEditor, options);
|
||||||
|
|
||||||
install.SetGrammar(options.GetScopeByExtension(".md"));
|
install.SetGrammar(options.GetScopeByExtension(".md"));
|
||||||
|
|
||||||
@ -45,8 +45,8 @@ public partial class EntrySpecificationsView : ReactiveUserControl<EntrySpecific
|
|||||||
if (_previewScrollViewer != null)
|
if (_previewScrollViewer != null)
|
||||||
_previewScrollViewer.PropertyChanged -= PreviewScrollViewerOnPropertyChanged;
|
_previewScrollViewer.PropertyChanged -= PreviewScrollViewerOnPropertyChanged;
|
||||||
|
|
||||||
_editorScrollViewer = DescriptionEditor.GetVisualChildrenOfType<ScrollViewer>().FirstOrDefault();
|
_editorScrollViewer = VisualExtensions.GetVisualChildrenOfType<ScrollViewer>(DescriptionEditor).FirstOrDefault();
|
||||||
_previewScrollViewer = DescriptionPreview.GetVisualChildrenOfType<ScrollViewer>().FirstOrDefault();
|
_previewScrollViewer = VisualExtensions.GetVisualChildrenOfType<ScrollViewer>(DescriptionPreview).FirstOrDefault();
|
||||||
|
|
||||||
if (_editorScrollViewer != null)
|
if (_editorScrollViewer != null)
|
||||||
_editorScrollViewer.PropertyChanged += EditorScrollViewerOnPropertyChanged;
|
_editorScrollViewer.PropertyChanged += EditorScrollViewerOnPropertyChanged;
|
||||||
@ -22,7 +22,7 @@ using ReactiveUI.Validation.Extensions;
|
|||||||
using ReactiveUI.Validation.Helpers;
|
using ReactiveUI.Validation.Helpers;
|
||||||
using StrawberryShake;
|
using StrawberryShake;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||||
|
|
||||||
public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
|
public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
|
||||||
{
|
{
|
||||||
@ -52,12 +52,12 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
|
|||||||
.Subscribe();
|
.Subscribe();
|
||||||
SelectedCategories = selectedCategories;
|
SelectedCategories = selectedCategories;
|
||||||
|
|
||||||
this.ValidationRule(vm => vm.Name, s => !string.IsNullOrWhiteSpace(s), "Name is required");
|
this.ValidationRule<EntrySpecificationsViewModel, string>(vm => vm.Name, s => !string.IsNullOrWhiteSpace(s), "Name is required");
|
||||||
this.ValidationRule(vm => vm.Summary, s => !string.IsNullOrWhiteSpace(s), "Summary is required");
|
this.ValidationRule<EntrySpecificationsViewModel, string>(vm => vm.Summary, s => !string.IsNullOrWhiteSpace(s), "Summary is required");
|
||||||
ValidationHelper descriptionRule = this.ValidationRule(vm => vm.Description, s => !string.IsNullOrWhiteSpace(s), "Description is required");
|
ValidationHelper descriptionRule = this.ValidationRule<EntrySpecificationsViewModel, string>(vm => vm.Description, s => !string.IsNullOrWhiteSpace(s), "Description is required");
|
||||||
|
|
||||||
// These don't use inputs that support validation messages, do so manually
|
// These don't use inputs that support validation messages, do so manually
|
||||||
ValidationHelper iconRule = this.ValidationRule(vm => vm.IconBitmap, s => s != null, "Icon required");
|
ValidationHelper iconRule = this.ValidationRule<EntrySpecificationsViewModel, Bitmap>(vm => vm.IconBitmap, s => s != null, "Icon required");
|
||||||
ValidationHelper categoriesRule = this.ValidationRule(vm => vm.Categories, Categories.ToObservableChangeSet().AutoRefresh(c => c.IsSelected).Filter(c => c.IsSelected).IsNotEmpty(),
|
ValidationHelper categoriesRule = this.ValidationRule(vm => vm.Categories, Categories.ToObservableChangeSet().AutoRefresh(c => c.IsSelected).Filter(c => c.IsSelected).IsNotEmpty(),
|
||||||
"At least one category must be selected"
|
"At least one category must be selected"
|
||||||
);
|
);
|
||||||
@ -1,8 +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"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
|
||||||
x:Class="Artemis.UI.Screens.Workshop.Entries.EntryInstallationDialogView">
|
|
||||||
Welcome to Avalonia!
|
|
||||||
</UserControl>
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
|
||||||
|
|
||||||
public partial class EntryInstallationDialogView : UserControl
|
|
||||||
{
|
|
||||||
public EntryInstallationDialogView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
using Artemis.UI.Shared;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
|
||||||
|
|
||||||
public class EntryInstallationDialogViewModel : ContentDialogViewModelBase
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -4,9 +4,10 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:entries="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
|
xmlns:entries="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
|
||||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||||
|
xmlns:list="clr-namespace:Artemis.UI.Screens.Workshop.Entries.List"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Workshop.Entries.EntryListInputView"
|
x:Class="Artemis.UI.Screens.Workshop.Entries.List.EntryListInputView"
|
||||||
x:DataType="entries:EntryListInputViewModel">
|
x:DataType="list:EntryListInputViewModel">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*" MaxWidth="500" />
|
<ColumnDefinition Width="*" MaxWidth="500" />
|
||||||
@ -1,8 +1,6 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
namespace Artemis.UI.Screens.Workshop.Entries.List;
|
||||||
|
|
||||||
public partial class EntryListInputView : UserControl
|
public partial class EntryListInputView : UserControl
|
||||||
{
|
{
|
||||||
@ -4,7 +4,7 @@ using Artemis.UI.Shared;
|
|||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
namespace Artemis.UI.Screens.Workshop.Entries.List;
|
||||||
|
|
||||||
public partial class EntryListInputViewModel : ViewModelBase
|
public partial class EntryListInputViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
@ -6,9 +6,10 @@
|
|||||||
xmlns:entries1="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
|
xmlns:entries1="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
|
||||||
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
xmlns:il="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||||
|
xmlns:list="clr-namespace:Artemis.UI.Screens.Workshop.Entries.List"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="110"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="110"
|
||||||
x:Class="Artemis.UI.Screens.Workshop.Entries.EntryListItemView"
|
x:Class="Artemis.UI.Screens.Workshop.Entries.List.EntryListItemView"
|
||||||
x:DataType="entries1:EntryListItemViewModel">
|
x:DataType="list:EntryListItemViewModel">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
|
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
|
||||||
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
||||||
@ -1,6 +1,6 @@
|
|||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
namespace Artemis.UI.Screens.Workshop.Entries.List;
|
||||||
|
|
||||||
public partial class EntryListItemView : ReactiveUserControl<EntryListItemViewModel>
|
public partial class EntryListItemView : ReactiveUserControl<EntryListItemViewModel>
|
||||||
{
|
{
|
||||||
@ -1,17 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using System.Reactive.Disposables;
|
|
||||||
using System.Reactive.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
using Artemis.WebClient.Workshop.Services;
|
|
||||||
using Avalonia.Media.Imaging;
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
namespace Artemis.UI.Screens.Workshop.Entries.List;
|
||||||
|
|
||||||
public class EntryListItemViewModel : ActivatableViewModelBase
|
public class EntryListItemViewModel : ActivatableViewModelBase
|
||||||
{
|
{
|
||||||
@ -17,7 +17,7 @@ using PropertyChanged.SourceGenerator;
|
|||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using StrawberryShake;
|
using StrawberryShake;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Entries;
|
namespace Artemis.UI.Screens.Workshop.Entries.List;
|
||||||
|
|
||||||
public abstract partial class EntryListViewModel : RoutableScreen<WorkshopListParameters>
|
public abstract partial class EntryListViewModel : RoutableScreen<WorkshopListParameters>
|
||||||
{
|
{
|
||||||
@ -42,8 +42,8 @@ public abstract partial class EntryListViewModel : RoutableScreen<WorkshopListPa
|
|||||||
_route = route;
|
_route = route;
|
||||||
_workshopClient = workshopClient;
|
_workshopClient = workshopClient;
|
||||||
_notificationService = notificationService;
|
_notificationService = notificationService;
|
||||||
_showPagination = this.WhenAnyValue(vm => vm.TotalPages).Select(t => t > 1).ToProperty(this, vm => vm.ShowPagination);
|
_showPagination = this.WhenAnyValue<EntryListViewModel, int>(vm => vm.TotalPages).Select(t => t > 1).ToProperty(this, vm => vm.ShowPagination);
|
||||||
_isLoading = this.WhenAnyValue(vm => vm.Page, vm => vm.LoadedPage, (p, c) => p != c).ToProperty(this, vm => vm.IsLoading);
|
_isLoading = this.WhenAnyValue<EntryListViewModel, bool, int, int>(vm => vm.Page, vm => vm.LoadedPage, (p, c) => p != c).ToProperty(this, vm => vm.IsLoading);
|
||||||
|
|
||||||
CategoriesViewModel = categoriesViewModel;
|
CategoriesViewModel = categoriesViewModel;
|
||||||
InputViewModel = entryListInputViewModel;
|
InputViewModel = entryListInputViewModel;
|
||||||
@ -56,7 +56,7 @@ public abstract partial class EntryListViewModel : RoutableScreen<WorkshopListPa
|
|||||||
Entries = entries;
|
Entries = entries;
|
||||||
|
|
||||||
// Respond to page changes
|
// Respond to page changes
|
||||||
this.WhenAnyValue(vm => vm.Page).Skip(1).Subscribe(p => Task.Run(() => router.Navigate($"{_route}/{p}")));
|
this.WhenAnyValue<EntryListViewModel, int>(vm => vm.Page).Skip(1).Subscribe(p => Task.Run(() => router.Navigate($"{_route}/{p}")));
|
||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
@ -1,17 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using Artemis.UI.Screens.Workshop.Categories;
|
using Artemis.UI.Screens.Workshop.Categories;
|
||||||
|
using Artemis.UI.Screens.Workshop.Entries.List;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Entries.Tabs;
|
namespace Artemis.UI.Screens.Workshop.Entries.Tabs;
|
||||||
|
|
||||||
public class LayoutListViewModel : EntryListViewModel
|
public class LayoutListViewModel : List.EntryListViewModel
|
||||||
{
|
{
|
||||||
public LayoutListViewModel(IWorkshopClient workshopClient,
|
public LayoutListViewModel(IWorkshopClient workshopClient,
|
||||||
IRouter router,
|
IRouter router,
|
||||||
CategoriesViewModel categoriesViewModel,
|
CategoriesViewModel categoriesViewModel,
|
||||||
EntryListInputViewModel entryListInputViewModel,
|
List.EntryListInputViewModel entryListInputViewModel,
|
||||||
INotificationService notificationService,
|
INotificationService notificationService,
|
||||||
Func<IGetEntries_Entries_Items, EntryListItemViewModel> getEntryListViewModel)
|
Func<IGetEntries_Entries_Items, EntryListItemViewModel> getEntryListViewModel)
|
||||||
: base("workshop/entries/layout", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
|
: base("workshop/entries/layout", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
|
||||||
|
|||||||
@ -1,17 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using Artemis.UI.Screens.Workshop.Categories;
|
using Artemis.UI.Screens.Workshop.Categories;
|
||||||
|
using Artemis.UI.Screens.Workshop.Entries.List;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Entries.Tabs;
|
namespace Artemis.UI.Screens.Workshop.Entries.Tabs;
|
||||||
|
|
||||||
public class ProfileListViewModel : EntryListViewModel
|
public class ProfileListViewModel : List.EntryListViewModel
|
||||||
{
|
{
|
||||||
public ProfileListViewModel(IWorkshopClient workshopClient,
|
public ProfileListViewModel(IWorkshopClient workshopClient,
|
||||||
IRouter router,
|
IRouter router,
|
||||||
CategoriesViewModel categoriesViewModel,
|
CategoriesViewModel categoriesViewModel,
|
||||||
EntryListInputViewModel entryListInputViewModel,
|
List.EntryListInputViewModel entryListInputViewModel,
|
||||||
INotificationService notificationService,
|
INotificationService notificationService,
|
||||||
Func<IGetEntries_Entries_Items, EntryListItemViewModel> getEntryListViewModel)
|
Func<IGetEntries_Entries_Items, EntryListItemViewModel> getEntryListViewModel)
|
||||||
: base("workshop/entries/profiles", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
|
: base("workshop/entries/profiles", workshopClient, router, categoriesViewModel, entryListInputViewModel, notificationService, getEntryListViewModel)
|
||||||
|
|||||||
@ -13,29 +13,34 @@
|
|||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<Border Classes="card" Padding="0" Width="300" ClipToBounds="True" Margin="5">
|
<Border Classes="card" Padding="0" Width="300" ClipToBounds="True" Margin="5">
|
||||||
<Grid RowDefinitions="230,*">
|
<Grid RowDefinitions="230,*">
|
||||||
<Rectangle Grid.Row="0" Fill="{DynamicResource CheckerboardBrush}"/>
|
<Rectangle Grid.Row="0" Fill="{DynamicResource CheckerboardBrush}" />
|
||||||
<Image Grid.Row="0"
|
<Image Grid.Row="0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||||
Source="{CompiledBinding Bitmap}"/>
|
Source="{CompiledBinding Bitmap}" />
|
||||||
<StackPanel Grid.Row="1" Margin="12">
|
<StackPanel Grid.Row="1" Margin="12">
|
||||||
<TextBlock Text="{CompiledBinding FileName, FallbackValue=Unnamed image}" TextTrimming="CharacterEllipsis" />
|
<TextBlock Text="{CompiledBinding Name, FallbackValue=Unnamed image}" TextTrimming="CharacterEllipsis" />
|
||||||
<StackPanel>
|
<TextBlock TextWrapping="Wrap" Classes="subtitle" Text="{CompiledBinding Description}" />
|
||||||
<TextBlock TextWrapping="Wrap" Classes="subtitle" Text="{CompiledBinding ImageDimensions, Mode=OneWay}" />
|
<Separator Margin="-4 10" />
|
||||||
<TextBlock TextWrapping="Wrap" Classes="subtitle" Text="{CompiledBinding FileSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay}" />
|
<TextBlock TextWrapping="Wrap" Classes="subtitle">
|
||||||
</StackPanel>
|
<Run Text="{CompiledBinding ImageDimensions}" /> - <Run Text="{CompiledBinding FileSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay}" />
|
||||||
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Button Grid.Row="1"
|
<StackPanel Grid.Row="1" Spacing="5" Margin="6" VerticalAlignment="Bottom" HorizontalAlignment="Right">
|
||||||
VerticalAlignment="Bottom"
|
<Button Classes="icon-button"
|
||||||
HorizontalAlignment="Right"
|
Command="{CompiledBinding Remove}"
|
||||||
Margin="6"
|
ToolTip.Tip="Edit">
|
||||||
Classes="icon-button"
|
<avalonia:MaterialIcon Kind="Edit" />
|
||||||
Command="{CompiledBinding Remove}"
|
</Button>
|
||||||
ToolTip.Tip="Remove">
|
<Button Classes="icon-button"
|
||||||
<avalonia:MaterialIcon Kind="Trash" />
|
Command="{CompiledBinding Remove}"
|
||||||
</Button>
|
ToolTip.Tip="Remove">
|
||||||
|
<avalonia:MaterialIcon Kind="Trash" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -2,39 +2,43 @@ using System.IO;
|
|||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
using ReactiveUI.Validation.Extensions;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Image;
|
namespace Artemis.UI.Screens.Workshop.Image;
|
||||||
|
|
||||||
public partial class ImageSubmissionViewModel : ActivatableViewModelBase
|
public partial class ImageSubmissionViewModel : ValidatableViewModelBase
|
||||||
{
|
{
|
||||||
[Notify(Setter.Private)] private Bitmap? _bitmap;
|
[Notify(Setter.Private)] private Bitmap? _bitmap;
|
||||||
[Notify(Setter.Private)] private string? _fileName;
|
|
||||||
[Notify(Setter.Private)] private string? _imageDimensions;
|
[Notify(Setter.Private)] private string? _imageDimensions;
|
||||||
[Notify(Setter.Private)] private long _fileSize;
|
[Notify(Setter.Private)] private long _fileSize;
|
||||||
|
[Notify] private string? _name;
|
||||||
|
[Notify] private string? _description;
|
||||||
[Notify] private ICommand? _remove;
|
[Notify] private ICommand? _remove;
|
||||||
|
|
||||||
public ImageSubmissionViewModel(Stream imageStream)
|
public ImageSubmissionViewModel(ImageUploadRequest image)
|
||||||
{
|
{
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Invoke(() =>
|
Dispatcher.UIThread.Invoke(() =>
|
||||||
{
|
{
|
||||||
imageStream.Seek(0, SeekOrigin.Begin);
|
image.File.Seek(0, SeekOrigin.Begin);
|
||||||
Bitmap = new Bitmap(imageStream);
|
Bitmap = new Bitmap(image.File);
|
||||||
FileSize = imageStream.Length;
|
FileSize = image.File.Length;
|
||||||
ImageDimensions = Bitmap.Size.Width + "x" + Bitmap.Size.Height;
|
ImageDimensions = Bitmap.Size.Width + "x" + Bitmap.Size.Height;
|
||||||
|
Name = image.Name;
|
||||||
if (imageStream is FileStream fileStream)
|
Description = image.Description;
|
||||||
FileName = Path.GetFileName(fileStream.Name);
|
|
||||||
else
|
|
||||||
FileName = "Unnamed image";
|
|
||||||
|
|
||||||
Bitmap.DisposeWith(d);
|
Bitmap.DisposeWith(d);
|
||||||
}, DispatcherPriority.Background);
|
}, DispatcherPriority.Background);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.ValidationRule(vm => vm.Name, input => !string.IsNullOrWhiteSpace(input), "Name is required");
|
||||||
|
this.ValidationRule(vm => vm.Name, input => input?.Length <= 50, "Name can be a maximum of 50 characters");
|
||||||
|
this.ValidationRule(vm => vm.Description, input => input?.Length <= 150, "Description can be a maximum of 150 characters");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,132 +2,31 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:Layout="clr-namespace:Artemis.UI.Screens.Workshop.Layout"
|
xmlns:layout="clr-namespace:Artemis.UI.Screens.Workshop.Layout"
|
||||||
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"
|
|
||||||
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
|
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
|
||||||
xmlns:mdsvg="https://github.com/whistyun/Markdown.Avalonia.Svg"
|
|
||||||
xmlns:ui="clr-namespace:Artemis.UI"
|
|
||||||
xmlns:converters1="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
|
||||||
x:Class="Artemis.UI.Screens.Workshop.Layout.LayoutDetailsView"
|
x:Class="Artemis.UI.Screens.Workshop.Layout.LayoutDetailsView"
|
||||||
x:DataType="Layout:LayoutDetailsViewModel">
|
x:DataType="layout:LayoutDetailsViewModel">
|
||||||
<UserControl.Resources>
|
<Grid ColumnDefinitions="300,*, 300" RowDefinitions="Auto,*">
|
||||||
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
|
|
||||||
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
|
||||||
<converters1:BytesToStringConverter x:Key="BytesToStringConverter" />
|
|
||||||
</UserControl.Resources>
|
|
||||||
<Grid ColumnDefinitions="300,*" RowDefinitions="Auto,*">
|
|
||||||
<StackPanel Grid.Row="1" Grid.Column="0" Margin="0 0 10 0" Spacing="10">
|
<StackPanel Grid.Row="1" Grid.Column="0" Margin="0 0 10 0" Spacing="10">
|
||||||
<Border Classes="card" VerticalAlignment="Top">
|
<Border Classes="card" VerticalAlignment="Top">
|
||||||
<StackPanel>
|
<ContentControl Content="{CompiledBinding EntryInfoViewModel}" />
|
||||||
<Border CornerRadius="6"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
Margin="0 0 10 0"
|
|
||||||
Width="80"
|
|
||||||
Height="80"
|
|
||||||
ClipToBounds="True">
|
|
||||||
<Image Stretch="UniformToFill" il:ImageLoader.Source="{CompiledBinding Entry.Id, Converter={StaticResource EntryIconUriConverter}, Mode=OneWay}" />
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}"
|
|
||||||
MaxLines="3"
|
|
||||||
TextTrimming="CharacterEllipsis"
|
|
||||||
Text="{CompiledBinding Entry.Name, FallbackValue=Title }" />
|
|
||||||
|
|
||||||
<TextBlock Classes="subtitle" TextTrimming="CharacterEllipsis" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
|
|
||||||
|
|
||||||
<TextBlock Margin="0 8" TextWrapping="Wrap" Text="{CompiledBinding Entry.Summary, FallbackValue=Summary}" />
|
|
||||||
|
|
||||||
<!-- Categories -->
|
|
||||||
<ItemsControl ItemsSource="{CompiledBinding Entry.Categories}" Margin="0 0 -8 0">
|
|
||||||
<ItemsControl.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<WrapPanel Orientation="Horizontal"></WrapPanel>
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ItemsControl.ItemsPanel>
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<StackPanel Orientation="Horizontal" Margin="0 0 8 0">
|
|
||||||
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Margin="0 0 3 0"></avalonia:MaterialIcon>
|
|
||||||
<TextBlock Text="{CompiledBinding Name}" TextTrimming="CharacterEllipsis" />
|
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
</ItemsControl>
|
|
||||||
|
|
||||||
<Border Classes="card-separator"></Border>
|
|
||||||
|
|
||||||
<TextBlock Margin="0 0 0 8">
|
|
||||||
<avalonia:MaterialIcon Kind="Downloads" />
|
|
||||||
<Run Classes="h5" Text="{CompiledBinding Entry.Downloads, FallbackValue=0}" />
|
|
||||||
<Run>downloads</Run>
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<TextBlock Classes="subtitle"
|
|
||||||
ToolTip.Tip="{CompiledBinding Entry.CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
|
||||||
<avalonia:MaterialIcon Kind="Calendar" />
|
|
||||||
<Run>Created</Run>
|
|
||||||
<Run Text="{CompiledBinding Entry.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
|
||||||
</TextBlock>
|
|
||||||
<TextBlock Classes="subtitle"
|
|
||||||
ToolTip.Tip="{CompiledBinding UpdatedAt, Converter={StaticResource DateTimeConverter}}">
|
|
||||||
<avalonia:MaterialIcon Kind="Update" />
|
|
||||||
<Run>Updated</Run>
|
|
||||||
<Run Text="{CompiledBinding UpdatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<Border Classes="card" VerticalAlignment="Top" IsVisible="{CompiledBinding Entry.LatestRelease, Converter={x:Static ObjectConverters.IsNotNull}}">
|
<Border Classes="card" VerticalAlignment="Top" IsVisible="{CompiledBinding Entry.LatestRelease, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||||
<StackPanel>
|
<ContentControl Content="{CompiledBinding EntryReleasesViewModel}" />
|
||||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Latest release</TextBlock>
|
|
||||||
<Border Classes="card-separator" />
|
|
||||||
<Button HorizontalAlignment="Stretch"
|
|
||||||
HorizontalContentAlignment="Stretch"
|
|
||||||
Command="{CompiledBinding DownloadLatestRelease}">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- Body -->
|
|
||||||
<StackPanel Grid.Column="1" Margin="10 0" VerticalAlignment="Center">
|
|
||||||
<TextBlock Text="{CompiledBinding Entry.LatestRelease.Version, FallbackValue=Version}"></TextBlock>
|
|
||||||
<TextBlock Classes="subtitle">
|
|
||||||
<avalonia:MaterialIcon Kind="BoxOutline" />
|
|
||||||
<Run Text="{CompiledBinding Entry.LatestRelease.DownloadSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay}"></Run>
|
|
||||||
</TextBlock>
|
|
||||||
<TextBlock Classes="subtitle"
|
|
||||||
ToolTip.Tip="{CompiledBinding Entry.LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
|
||||||
<avalonia:MaterialIcon Kind="Calendar" />
|
|
||||||
<Run>Created</Run>
|
|
||||||
<Run Text="{CompiledBinding Entry.LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|
||||||
<Border Classes="card" Grid.Row="1" Grid.Column="1">
|
<Border Classes="card" Grid.Row="1" Grid.Column="1">
|
||||||
<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" />
|
||||||
</mdxaml:MarkdownScrollViewer.Styles>
|
</mdxaml:MarkdownScrollViewer.Styles>
|
||||||
</mdxaml:MarkdownScrollViewer>
|
</mdxaml:MarkdownScrollViewer>
|
||||||
|
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" Grid.Column="2" IsVisible="{CompiledBinding Entry.Images.Count}">
|
||||||
|
<ContentControl Content="{CompiledBinding EntryImagesViewModel}" />
|
||||||
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,17 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Reactive;
|
|
||||||
using System.Reactive.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
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.UI.Shared.Services;
|
|
||||||
using Artemis.UI.Shared.Services.Builders;
|
|
||||||
using Artemis.UI.Shared.Utilities;
|
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using ReactiveUI;
|
|
||||||
using StrawberryShake;
|
using StrawberryShake;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Layout;
|
namespace Artemis.UI.Screens.Workshop.Layout;
|
||||||
@ -19,25 +13,25 @@ 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 INotificationService _notificationService;
|
private readonly Func<IGetEntryById_Entry, EntryInfoViewModel> _getEntryInfoViewModel;
|
||||||
private readonly IWindowService _windowService;
|
private readonly Func<IGetEntryById_Entry, EntryReleasesViewModel> _getEntryReleasesViewModel;
|
||||||
private readonly ObservableAsPropertyHelper<DateTimeOffset?> _updatedAt;
|
private readonly Func<IGetEntryById_Entry, EntryImagesViewModel> _getEntryImagesViewModel;
|
||||||
[Notify(Setter.Private)] private IGetEntryById_Entry? _entry;
|
[Notify] private IGetEntryById_Entry? _entry;
|
||||||
|
[Notify] private EntryInfoViewModel? _entryInfoViewModel;
|
||||||
|
[Notify] private EntryReleasesViewModel? _entryReleasesViewModel;
|
||||||
|
[Notify] private EntryImagesViewModel? _entryImagesViewModel;
|
||||||
|
|
||||||
public LayoutDetailsViewModel(IWorkshopClient client, INotificationService notificationService, IWindowService windowService)
|
public LayoutDetailsViewModel(IWorkshopClient client,
|
||||||
|
Func<IGetEntryById_Entry, EntryInfoViewModel> getEntryInfoViewModel,
|
||||||
|
Func<IGetEntryById_Entry, EntryReleasesViewModel> getEntryReleasesViewModel,
|
||||||
|
Func<IGetEntryById_Entry, EntryImagesViewModel> getEntryImagesViewModel)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
_notificationService = notificationService;
|
_getEntryInfoViewModel = getEntryInfoViewModel;
|
||||||
_windowService = windowService;
|
_getEntryReleasesViewModel = getEntryReleasesViewModel;
|
||||||
_updatedAt = this.WhenAnyValue(vm => vm.Entry).Select(e => e?.LatestRelease?.CreatedAt ?? e?.CreatedAt).ToProperty(this, vm => vm.UpdatedAt);
|
_getEntryImagesViewModel = getEntryImagesViewModel;
|
||||||
|
|
||||||
DownloadLatestRelease = ReactiveCommand.CreateFromTask(ExecuteDownloadLatestRelease);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> DownloadLatestRelease { get; }
|
|
||||||
|
|
||||||
public DateTimeOffset? UpdatedAt => _updatedAt.Value;
|
|
||||||
|
|
||||||
public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
|
public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await GetEntry(parameters.EntryId, cancellationToken);
|
await GetEntry(parameters.EntryId, cancellationToken);
|
||||||
@ -50,10 +44,16 @@ public partial class LayoutDetailsViewModel : RoutableScreen<WorkshopDetailParam
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
Entry = result.Data?.Entry;
|
Entry = result.Data?.Entry;
|
||||||
}
|
if (Entry == null)
|
||||||
|
{
|
||||||
private Task ExecuteDownloadLatestRelease(CancellationToken cancellationToken)
|
EntryInfoViewModel = null;
|
||||||
{
|
EntryReleasesViewModel = null;
|
||||||
return Task.CompletedTask;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EntryInfoViewModel = _getEntryInfoViewModel(Entry);
|
||||||
|
EntryReleasesViewModel = _getEntryReleasesViewModel(Entry);
|
||||||
|
EntryImagesViewModel = _getEntryImagesViewModel(Entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -26,8 +26,9 @@
|
|||||||
<Button Grid.Row="1"
|
<Button Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.ColumnSpan="2"
|
Grid.ColumnSpan="2"
|
||||||
|
Margin="0 25 0 0"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Bottom"
|
VerticalAlignment="Top"
|
||||||
Classes="AppBarButton"
|
Classes="AppBarButton"
|
||||||
Command="{CompiledBinding BrowseDeviceProvider}"
|
Command="{CompiledBinding BrowseDeviceProvider}"
|
||||||
ToolTip.Tip="Browse">
|
ToolTip.Tip="Browse">
|
||||||
|
|||||||
@ -51,6 +51,7 @@ public partial class LayoutInfoViewModel : ValidatableViewModelBase
|
|||||||
this.ValidationRule(vm => vm.Vendor, input => !string.IsNullOrWhiteSpace(input), "Device vendor is required");
|
this.ValidationRule(vm => vm.Vendor, input => !string.IsNullOrWhiteSpace(input), "Device vendor is required");
|
||||||
this.ValidationRule(vm => vm.DeviceProviderIdInput, input => Guid.TryParse(input, out _), "Must be a valid GUID formatted as: 00000000-0000-0000-0000-000000000000");
|
this.ValidationRule(vm => vm.DeviceProviderIdInput, input => Guid.TryParse(input, out _), "Must be a valid GUID formatted as: 00000000-0000-0000-0000-000000000000");
|
||||||
this.ValidationRule(vm => vm.DeviceProviderIdInput, input => !string.IsNullOrWhiteSpace(input), "Device provider ID is required");
|
this.ValidationRule(vm => vm.DeviceProviderIdInput, input => !string.IsNullOrWhiteSpace(input), "Device provider ID is required");
|
||||||
|
this.ValidationRule(vm => vm.DeviceProviderIdInput, input => input != "00000000-0000-0000-0000-000000000000", "Device provider ID is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? DeviceProviders => _deviceProviders.Value;
|
public string? DeviceProviders => _deviceProviders.Value;
|
||||||
|
|||||||
@ -21,6 +21,7 @@ using Avalonia.Media.Imaging;
|
|||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using StrawberryShake;
|
using StrawberryShake;
|
||||||
|
using EntrySpecificationsViewModel = Artemis.UI.Screens.Workshop.Entries.Details.EntrySpecificationsViewModel;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Library;
|
namespace Artemis.UI.Screens.Workshop.Library;
|
||||||
|
|
||||||
|
|||||||
@ -3,141 +3,30 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:profile="clr-namespace:Artemis.UI.Screens.Workshop.Profile"
|
xmlns:profile="clr-namespace:Artemis.UI.Screens.Workshop.Profile"
|
||||||
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"
|
|
||||||
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
|
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
|
||||||
xmlns:mdsvg="https://github.com/whistyun/Markdown.Avalonia.Svg"
|
|
||||||
xmlns:ui="clr-namespace:Artemis.UI"
|
|
||||||
xmlns:converters1="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
|
||||||
x:Class="Artemis.UI.Screens.Workshop.Profile.ProfileDetailsView"
|
x:Class="Artemis.UI.Screens.Workshop.Profile.ProfileDetailsView"
|
||||||
x:DataType="profile:ProfileDetailsViewModel">
|
x:DataType="profile:ProfileDetailsViewModel">
|
||||||
<UserControl.Resources>
|
<Grid ColumnDefinitions="300,*, 300" RowDefinitions="Auto,*">
|
||||||
<converters:EntryIconUriConverter x:Key="EntryIconUriConverter" />
|
|
||||||
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
|
||||||
<converters1:BytesToStringConverter x:Key="BytesToStringConverter" />
|
|
||||||
</UserControl.Resources>
|
|
||||||
<Grid ColumnDefinitions="300,*" RowDefinitions="Auto,*">
|
|
||||||
<StackPanel Grid.Row="1" Grid.Column="0" Margin="0 0 10 0" Spacing="10">
|
<StackPanel Grid.Row="1" Grid.Column="0" Margin="0 0 10 0" Spacing="10">
|
||||||
<Border Classes="card" VerticalAlignment="Top">
|
<Border Classes="card" VerticalAlignment="Top">
|
||||||
<StackPanel>
|
<ContentControl Content="{CompiledBinding EntryInfoViewModel}" />
|
||||||
<Panel>
|
|
||||||
<Border CornerRadius="6"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
Margin="0 0 10 0"
|
|
||||||
Width="80"
|
|
||||||
Height="80"
|
|
||||||
ClipToBounds="True">
|
|
||||||
<Image Stretch="UniformToFill" il:ImageLoader.Source="{CompiledBinding Entry.Id, Converter={StaticResource EntryIconUriConverter}, Mode=OneWay}" />
|
|
||||||
</Border>
|
|
||||||
<Button Classes="icon-button"
|
|
||||||
VerticalAlignment="Top"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
Command="{CompiledBinding CopyShareLink}"
|
|
||||||
ToolTip.Tip="Copy share link">
|
|
||||||
<avalonia:MaterialIcon Kind="ShareVariant"/>
|
|
||||||
</Button>
|
|
||||||
</Panel>
|
|
||||||
|
|
||||||
|
|
||||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}"
|
|
||||||
MaxLines="3"
|
|
||||||
TextTrimming="CharacterEllipsis"
|
|
||||||
Text="{CompiledBinding Entry.Name, FallbackValue=Title }" />
|
|
||||||
|
|
||||||
<TextBlock Classes="subtitle" TextTrimming="CharacterEllipsis" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
|
|
||||||
|
|
||||||
<TextBlock Margin="0 8" TextWrapping="Wrap" Text="{CompiledBinding Entry.Summary, FallbackValue=Summary}" />
|
|
||||||
|
|
||||||
<!-- Categories -->
|
|
||||||
<ItemsControl ItemsSource="{CompiledBinding Entry.Categories}" Margin="0 0 -8 0">
|
|
||||||
<ItemsControl.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<WrapPanel Orientation="Horizontal"></WrapPanel>
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ItemsControl.ItemsPanel>
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<StackPanel Orientation="Horizontal" Margin="0 0 8 0">
|
|
||||||
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Margin="0 0 3 0"></avalonia:MaterialIcon>
|
|
||||||
<TextBlock Text="{CompiledBinding Name}" TextTrimming="CharacterEllipsis" />
|
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
</ItemsControl>
|
|
||||||
|
|
||||||
<Border Classes="card-separator"></Border>
|
|
||||||
|
|
||||||
<TextBlock Margin="0 0 0 8">
|
|
||||||
<avalonia:MaterialIcon Kind="Downloads" />
|
|
||||||
<Run Classes="h5" Text="{CompiledBinding Entry.Downloads, FallbackValue=0}" />
|
|
||||||
<Run>downloads</Run>
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<TextBlock Classes="subtitle"
|
|
||||||
ToolTip.Tip="{CompiledBinding Entry.CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
|
||||||
<avalonia:MaterialIcon Kind="Calendar" />
|
|
||||||
<Run>Created</Run>
|
|
||||||
<Run Text="{CompiledBinding Entry.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
|
||||||
</TextBlock>
|
|
||||||
<TextBlock Classes="subtitle"
|
|
||||||
ToolTip.Tip="{CompiledBinding UpdatedAt, Converter={StaticResource DateTimeConverter}}">
|
|
||||||
<avalonia:MaterialIcon Kind="Update" />
|
|
||||||
<Run>Updated</Run>
|
|
||||||
<Run Text="{CompiledBinding UpdatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<Border Classes="card" VerticalAlignment="Top" IsVisible="{CompiledBinding Entry.LatestRelease, Converter={x:Static ObjectConverters.IsNotNull}}">
|
<Border Classes="card" VerticalAlignment="Top" IsVisible="{CompiledBinding Entry.LatestRelease, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||||
<StackPanel>
|
<ContentControl Content="{CompiledBinding EntryReleasesViewModel}" />
|
||||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Latest release</TextBlock>
|
|
||||||
<Border Classes="card-separator" />
|
|
||||||
<Button HorizontalAlignment="Stretch"
|
|
||||||
HorizontalContentAlignment="Stretch"
|
|
||||||
Command="{CompiledBinding DownloadLatestRelease}">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- Body -->
|
|
||||||
<StackPanel Grid.Column="1" Margin="10 0" VerticalAlignment="Center">
|
|
||||||
<TextBlock Text="{CompiledBinding Entry.LatestRelease.Version, FallbackValue=Version}"></TextBlock>
|
|
||||||
<TextBlock Classes="subtitle">
|
|
||||||
<avalonia:MaterialIcon Kind="BoxOutline" />
|
|
||||||
<Run Text="{CompiledBinding Entry.LatestRelease.DownloadSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay}"></Run>
|
|
||||||
</TextBlock>
|
|
||||||
<TextBlock Classes="subtitle"
|
|
||||||
ToolTip.Tip="{CompiledBinding Entry.LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
|
||||||
<avalonia:MaterialIcon Kind="Calendar" />
|
|
||||||
<Run>Created</Run>
|
|
||||||
<Run Text="{CompiledBinding Entry.LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|
||||||
<Border Classes="card" Grid.Row="1" Grid.Column="1">
|
<Border Classes="card" Grid.Row="1" Grid.Column="1">
|
||||||
<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" />
|
||||||
</mdxaml:MarkdownScrollViewer.Styles>
|
</mdxaml:MarkdownScrollViewer.Styles>
|
||||||
</mdxaml:MarkdownScrollViewer>
|
</mdxaml:MarkdownScrollViewer>
|
||||||
|
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" Grid.Column="2" IsVisible="{CompiledBinding Entry.Images.Count}">
|
||||||
|
<ContentControl Content="{CompiledBinding EntryImagesViewModel}" />
|
||||||
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,18 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Reactive;
|
|
||||||
using System.Reactive.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.Core;
|
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.UI.Shared.Services;
|
|
||||||
using Artemis.UI.Shared.Services.Builders;
|
|
||||||
using Artemis.UI.Shared.Utilities;
|
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using ReactiveUI;
|
|
||||||
using StrawberryShake;
|
using StrawberryShake;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Profile;
|
namespace Artemis.UI.Screens.Workshop.Profile;
|
||||||
@ -20,30 +13,25 @@ namespace Artemis.UI.Screens.Workshop.Profile;
|
|||||||
public partial class ProfileDetailsViewModel : RoutableScreen<WorkshopDetailParameters>
|
public partial class ProfileDetailsViewModel : RoutableScreen<WorkshopDetailParameters>
|
||||||
{
|
{
|
||||||
private readonly IWorkshopClient _client;
|
private readonly IWorkshopClient _client;
|
||||||
private readonly ProfileEntryInstallationHandler _installationHandler;
|
private readonly Func<IGetEntryById_Entry, EntryInfoViewModel> _getEntryInfoViewModel;
|
||||||
private readonly INotificationService _notificationService;
|
private readonly Func<IGetEntryById_Entry, EntryReleasesViewModel> _getEntryReleasesViewModel;
|
||||||
private readonly ObservableAsPropertyHelper<DateTimeOffset?> _updatedAt;
|
private readonly Func<IGetEntryById_Entry, EntryImagesViewModel> _getEntryImagesViewModel;
|
||||||
private readonly IWindowService _windowService;
|
[Notify] private IGetEntryById_Entry? _entry;
|
||||||
[Notify(Setter.Private)] private IGetEntryById_Entry? _entry;
|
[Notify] private EntryInfoViewModel? _entryInfoViewModel;
|
||||||
|
[Notify] private EntryReleasesViewModel? _entryReleasesViewModel;
|
||||||
|
[Notify] private EntryImagesViewModel? _entryImagesViewModel;
|
||||||
|
|
||||||
public ProfileDetailsViewModel(IWorkshopClient client, ProfileEntryInstallationHandler installationHandler, INotificationService notificationService, IWindowService windowService)
|
public ProfileDetailsViewModel(IWorkshopClient client,
|
||||||
|
Func<IGetEntryById_Entry, EntryInfoViewModel> getEntryInfoViewModel,
|
||||||
|
Func<IGetEntryById_Entry, EntryReleasesViewModel> getEntryReleasesViewModel,
|
||||||
|
Func<IGetEntryById_Entry, EntryImagesViewModel> getEntryImagesViewModel)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
_installationHandler = installationHandler;
|
_getEntryInfoViewModel = getEntryInfoViewModel;
|
||||||
_notificationService = notificationService;
|
_getEntryReleasesViewModel = getEntryReleasesViewModel;
|
||||||
_windowService = windowService;
|
_getEntryImagesViewModel = getEntryImagesViewModel;
|
||||||
_updatedAt = this.WhenAnyValue(vm => vm.Entry).Select(e => e?.LatestRelease?.CreatedAt ?? e?.CreatedAt).ToProperty(this, vm => vm.UpdatedAt);
|
|
||||||
|
|
||||||
DownloadLatestRelease = ReactiveCommand.CreateFromTask(ExecuteDownloadLatestRelease);
|
|
||||||
CopyShareLink = ReactiveCommand.CreateFromTask(ExecuteCopyShareLink);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> CopyShareLink { get; set; }
|
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> DownloadLatestRelease { get; }
|
|
||||||
|
|
||||||
public DateTimeOffset? UpdatedAt => _updatedAt.Value;
|
|
||||||
|
|
||||||
public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
|
public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await GetEntry(parameters.EntryId, cancellationToken);
|
await GetEntry(parameters.EntryId, cancellationToken);
|
||||||
@ -56,30 +44,16 @@ public partial class ProfileDetailsViewModel : RoutableScreen<WorkshopDetailPara
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
Entry = result.Data?.Entry;
|
Entry = result.Data?.Entry;
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ExecuteDownloadLatestRelease(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (Entry?.LatestRelease == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
bool confirm = await _windowService.ShowConfirmContentDialog("Install profile?", "The profile will be downloaded and added to your sidebar automatically.");
|
|
||||||
if (!confirm)
|
|
||||||
return;
|
|
||||||
|
|
||||||
EntryInstallResult result = await _installationHandler.InstallAsync(Entry, Entry.LatestRelease.Id, new Progress<StreamProgress>(), cancellationToken);
|
|
||||||
if (result.IsSuccess)
|
|
||||||
_notificationService.CreateNotification().WithTitle("Profile installed").WithSeverity(NotificationSeverity.Success).Show();
|
|
||||||
else
|
|
||||||
_notificationService.CreateNotification().WithTitle("Failed to install profile").WithMessage(result.Message).WithSeverity(NotificationSeverity.Error).Show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ExecuteCopyShareLink(CancellationToken arg)
|
|
||||||
{
|
|
||||||
if (Entry == null)
|
if (Entry == null)
|
||||||
return;
|
{
|
||||||
|
EntryInfoViewModel = null;
|
||||||
await Shared.UI.Clipboard.SetTextAsync($"{WorkshopConstants.WORKSHOP_URL}/entries/{Entry.Id}/{StringUtilities.UrlFriendly(Entry.Name)}");
|
EntryReleasesViewModel = null;
|
||||||
_notificationService.CreateNotification().WithTitle("Copied share link to clipboard.").Show();
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EntryInfoViewModel = _getEntryInfoViewModel(Entry);
|
||||||
|
EntryReleasesViewModel = _getEntryReleasesViewModel(Entry);
|
||||||
|
EntryImagesViewModel = _getEntryImagesViewModel(Entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,7 +33,7 @@ public class SubmissionWizardState : IDisposable
|
|||||||
|
|
||||||
public List<long> Categories { get; set; } = new();
|
public List<long> Categories { get; set; } = new();
|
||||||
public List<string> Tags { get; set; } = new();
|
public List<string> Tags { get; set; } = new();
|
||||||
public List<Stream> Images { get; set; } = new();
|
public List<ImageUploadRequest> Images { get; set; } = new();
|
||||||
|
|
||||||
public IEntrySource? EntrySource { get; set; }
|
public IEntrySource? EntrySource { get; set; }
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ public class SubmissionWizardState : IDisposable
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Icon?.Dispose();
|
Icon?.Dispose();
|
||||||
foreach (Stream stream in Images)
|
foreach (ImageUploadRequest image in Images)
|
||||||
stream.Dispose();
|
image.File.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7,6 +7,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.UI.Screens.Workshop.Image;
|
using Artemis.UI.Screens.Workshop.Image;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
@ -16,37 +17,39 @@ public class ImagesStepViewModel : SubmissionViewModel
|
|||||||
{
|
{
|
||||||
private const long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
|
private const long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
|
||||||
private readonly IWindowService _windowService;
|
private readonly IWindowService _windowService;
|
||||||
private readonly SourceList<Stream> _imageStreams;
|
private readonly Func<ImageUploadRequest, ImageSubmissionViewModel> _getImageSubmissionViewModel;
|
||||||
|
private readonly SourceList<ImageUploadRequest> _stateImages;
|
||||||
|
|
||||||
public ImagesStepViewModel(IWindowService windowService, Func<Stream, ImageSubmissionViewModel> imageSubmissionViewModel)
|
public ImagesStepViewModel(IWindowService windowService, Func<ImageUploadRequest, ImageSubmissionViewModel> getImageSubmissionViewModel)
|
||||||
{
|
{
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
|
_getImageSubmissionViewModel = getImageSubmissionViewModel;
|
||||||
|
|
||||||
Continue = ReactiveCommand.Create(() => State.ChangeScreen<UploadStepViewModel>());
|
Continue = ReactiveCommand.Create(() => State.ChangeScreen<UploadStepViewModel>());
|
||||||
GoBack = ReactiveCommand.Create(() => State.ChangeScreen<SpecificationsStepViewModel>());
|
GoBack = ReactiveCommand.Create(() => State.ChangeScreen<SpecificationsStepViewModel>());
|
||||||
Secondary = ReactiveCommand.CreateFromTask(ExecuteAddImage);
|
Secondary = ReactiveCommand.CreateFromTask(ExecuteAddImage);
|
||||||
SecondaryText = "Add image";
|
SecondaryText = "Add image";
|
||||||
|
|
||||||
_imageStreams = new SourceList<Stream>();
|
_stateImages = new SourceList<ImageUploadRequest>();
|
||||||
_imageStreams.Connect()
|
_stateImages.Connect()
|
||||||
.Transform(p => CreateImageSubmissionViewModel(imageSubmissionViewModel, p))
|
.Transform(p => CreateImageSubmissionViewModel(p))
|
||||||
.Bind(out ReadOnlyObservableCollection<ImageSubmissionViewModel> images)
|
.Bind(out ReadOnlyObservableCollection<ImageSubmissionViewModel> images)
|
||||||
.Subscribe();
|
.Subscribe();
|
||||||
Images = images;
|
Images = images;
|
||||||
|
|
||||||
this.WhenActivated((CompositeDisposable d) =>
|
this.WhenActivated((CompositeDisposable d) =>
|
||||||
{
|
{
|
||||||
_imageStreams.Clear();
|
_stateImages.Clear();
|
||||||
_imageStreams.AddRange(State.Images);
|
_stateImages.AddRange(State.Images);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyObservableCollection<ImageSubmissionViewModel> Images { get; }
|
public ReadOnlyObservableCollection<ImageSubmissionViewModel> Images { get; }
|
||||||
|
|
||||||
private ImageSubmissionViewModel CreateImageSubmissionViewModel(Func<Stream, ImageSubmissionViewModel> imageSubmissionViewModel, Stream stream)
|
private ImageSubmissionViewModel CreateImageSubmissionViewModel(ImageUploadRequest image)
|
||||||
{
|
{
|
||||||
ImageSubmissionViewModel viewModel = imageSubmissionViewModel(stream);
|
ImageSubmissionViewModel viewModel = _getImageSubmissionViewModel(image);
|
||||||
viewModel.Remove = ReactiveCommand.Create(() => _imageStreams.Remove(stream));
|
viewModel.Remove = ReactiveCommand.Create(() => _stateImages.Remove(image));
|
||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +61,7 @@ public class ImagesStepViewModel : SubmissionViewModel
|
|||||||
|
|
||||||
foreach (string path in result)
|
foreach (string path in result)
|
||||||
{
|
{
|
||||||
if (_imageStreams.Items.Any(i => i is FileStream fs && fs.Name == path))
|
if (_stateImages.Items.Any(i => i.File is FileStream fs && fs.Name == path))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
FileStream stream = new(path, FileMode.Open, FileAccess.Read);
|
FileStream stream = new(path, FileMode.Open, FileAccess.Read);
|
||||||
@ -69,8 +72,9 @@ public class ImagesStepViewModel : SubmissionViewModel
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_imageStreams.Add(stream);
|
ImageUploadRequest request = new(stream, Path.GetFileName(path), string.Empty);
|
||||||
State.Images.Add(stream);
|
_stateImages.Add(request);
|
||||||
|
State.Images.Add(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,14 +166,14 @@ public partial class LayoutSelectionStepViewModel : SubmissionViewModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
State.Icon?.Dispose();
|
State.Icon?.Dispose();
|
||||||
foreach (Stream stateImage in State.Images)
|
foreach (ImageUploadRequest stateImage in State.Images)
|
||||||
stateImage.Dispose();
|
stateImage.File.Dispose();
|
||||||
State.Images.Clear();
|
State.Images.Clear();
|
||||||
|
|
||||||
// Go through the hassle of resizing the image to 128x128 without losing aspect ratio, padding is added for this
|
// Go through the hassle of resizing the image to 128x128 without losing aspect ratio, padding is added for this
|
||||||
State.Icon = ResizeImage(deviceWithoutLeds, 128);
|
State.Icon = ResizeImage(deviceWithoutLeds, 128);
|
||||||
State.Images.Add(deviceWithoutLeds);
|
State.Images.Add(new ImageUploadRequest(deviceWithoutLeds, "Layout preview (no LEDs)", "A preview of the device without its LEDs"));
|
||||||
State.Images.Add(deviceWithLeds);
|
State.Images.Add(new ImageUploadRequest(deviceWithLeds, "Layout preview (with LEDs)", "A preview of the device with its LEDs"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream ResizeImage(Stream image, int size)
|
private Stream ResizeImage(Stream image, int size)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ using Artemis.WebClient.Workshop;
|
|||||||
using DynamicData;
|
using DynamicData;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
using EntrySpecificationsViewModel = Artemis.UI.Screens.Workshop.Entries.Details.EntrySpecificationsViewModel;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps;
|
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps;
|
||||||
|
|
||||||
|
|||||||
@ -133,12 +133,12 @@ public partial class UploadStepViewModel : SubmissionViewModel
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Stream image in State.Images.ToList())
|
foreach (ImageUploadRequest image in State.Images.ToList())
|
||||||
{
|
{
|
||||||
// Upload image
|
// Upload image
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ImageUploadResult imageUploadResult = await _workshopService.UploadEntryImage(entryId.Value, _progress, image, cancellationToken);
|
ImageUploadResult imageUploadResult = await _workshopService.UploadEntryImage(entryId.Value, image, _progress, cancellationToken);
|
||||||
if (!imageUploadResult.IsSuccess)
|
if (!imageUploadResult.IsSuccess)
|
||||||
throw new ArtemisWorkshopException(imageUploadResult.Message);
|
throw new ArtemisWorkshopException(imageUploadResult.Message);
|
||||||
State.Images.Remove(image);
|
State.Images.Remove(image);
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
||||||
<PackageReference Include="ReactiveUI" Version="19.5.1" />
|
<PackageReference Include="ReactiveUI" Version="19.5.1" />
|
||||||
<PackageReference Include="StrawberryShake.Server" Version="13.6.0-preview.31" />
|
<PackageReference Include="StrawberryShake.Server" Version="13.7.0" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" />
|
||||||
<PackageReference Include="System.Reactive" Version="6.0.0" />
|
<PackageReference Include="System.Reactive" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@ -16,6 +16,7 @@ public class EntryInstallationHandlerFactory
|
|||||||
return entryType switch
|
return entryType switch
|
||||||
{
|
{
|
||||||
EntryType.Profile => _container.Resolve<ProfileEntryInstallationHandler>(),
|
EntryType.Profile => _container.Resolve<ProfileEntryInstallationHandler>(),
|
||||||
|
EntryType.Layout => _container.Resolve<LayoutEntryInstallationHandler>(),
|
||||||
_ => throw new NotSupportedException($"EntryType '{entryType}' is not supported.")
|
_ => throw new NotSupportedException($"EntryType '{entryType}' is not supported.")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,45 @@
|
|||||||
|
using Artemis.UI.Shared.Extensions;
|
||||||
|
using Artemis.UI.Shared.Utilities;
|
||||||
|
using Artemis.WebClient.Workshop.Services;
|
||||||
|
|
||||||
|
namespace Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
||||||
|
|
||||||
|
public class LayoutEntryInstallationHandler : IEntryInstallationHandler
|
||||||
|
{
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly IWorkshopService _workshopService;
|
||||||
|
|
||||||
|
public LayoutEntryInstallationHandler(IHttpClientFactory httpClientFactory, IWorkshopService workshopService)
|
||||||
|
{
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_workshopService = workshopService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EntryInstallResult> InstallAsync(IGetEntryById_Entry entry, long releaseId, Progress<StreamProgress> progress, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
|
||||||
|
using MemoryStream stream = new();
|
||||||
|
|
||||||
|
// Download the provided release
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
|
||||||
|
await client.DownloadDataAsync($"releases/download/{releaseId}", stream, progress, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return EntryInstallResult.FromFailure(e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return EntryInstallResult.FromSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
|
||||||
|
if (!Guid.TryParse(installedEntry.LocalReference, out Guid profileId))
|
||||||
|
return EntryUninstallResult.FromFailure("Local reference does not contain a GUID");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
||||||
|
|
||||||
|
public class ImageUploadRequest
|
||||||
|
{
|
||||||
|
public ImageUploadRequest(Stream file, string name, string? description)
|
||||||
|
{
|
||||||
|
File = file;
|
||||||
|
Name = name.Length > 50 ? name.Substring(0, 50) : name;
|
||||||
|
if (description != null)
|
||||||
|
Description = description.Length > 150 ? description.Substring(0, 150) : description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream File { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
}
|
||||||
@ -1,13 +1,23 @@
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Shared.Utilities;
|
using Artemis.UI.Shared.Utilities;
|
||||||
|
using Artemis.WebClient.Workshop.Entities;
|
||||||
using Artemis.WebClient.Workshop.Exceptions;
|
using Artemis.WebClient.Workshop.Exceptions;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using RGB.NET.Layout;
|
using RGB.NET.Layout;
|
||||||
|
|
||||||
namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
||||||
|
|
||||||
public class LayoutEntryUploadHandler : IEntryUploadHandler
|
public class LayoutEntryUploadHandler : IEntryUploadHandler
|
||||||
{
|
{
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
|
public LayoutEntryUploadHandler(IHttpClientFactory httpClientFactory)
|
||||||
|
{
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<EntryUploadResult> CreateReleaseAsync(long entryId, IEntrySource entrySource, Progress<StreamProgress> progress, CancellationToken cancellationToken)
|
public async Task<EntryUploadResult> CreateReleaseAsync(long entryId, IEntrySource entrySource, Progress<StreamProgress> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@ -46,15 +56,28 @@ public class LayoutEntryUploadHandler : IEntryUploadHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
archiveStream.Seek(0, SeekOrigin.Begin);
|
archiveStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
await using (FileStream fileStream = new(@"C:\Users\Robert\Desktop\layout-test.zip", FileMode.OpenOrCreate))
|
||||||
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
|
||||||
string filePath = Path.Combine(desktopPath, "layout-test.zip");
|
|
||||||
await using (FileStream fileStream = new(filePath, FileMode.Create, FileAccess.Write))
|
|
||||||
{
|
{
|
||||||
archiveStream.WriteTo(fileStream);
|
await archiveStream.CopyToAsync(fileStream, cancellationToken);
|
||||||
}
|
}
|
||||||
|
archiveStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
// Submit the archive
|
||||||
|
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
|
||||||
|
|
||||||
return new EntryUploadResult();
|
// Construct the request
|
||||||
|
MultipartFormDataContent content = new();
|
||||||
|
ProgressableStreamContent streamContent = new(archiveStream, progress);
|
||||||
|
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
|
||||||
|
content.Add(streamContent, "file", "file.zip");
|
||||||
|
|
||||||
|
// Submit
|
||||||
|
HttpResponseMessage response = await client.PostAsync("releases/upload/" + entryId, content, cancellationToken);
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
return EntryUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}");
|
||||||
|
|
||||||
|
Release? release = JsonConvert.DeserializeObject<Release>(await response.Content.ReadAsStringAsync(cancellationToken));
|
||||||
|
return release != null ? EntryUploadResult.FromSuccess(release) : EntryUploadResult.FromFailure("Failed to deserialize response");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CopyImage(string layoutPath, string? imagePath, ZipArchive archive)
|
private static void CopyImage(string layoutPath, string? imagePath, ZipArchive archive)
|
||||||
|
|||||||
@ -3,6 +3,12 @@ fragment category on Category {
|
|||||||
icon
|
icon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fragment image on Image {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
description
|
||||||
|
}
|
||||||
|
|
||||||
fragment layoutInfo on LayoutInfo {
|
fragment layoutInfo on LayoutInfo {
|
||||||
id
|
id
|
||||||
deviceProvider
|
deviceProvider
|
||||||
@ -20,4 +26,28 @@ fragment submittedEntry on Entry {
|
|||||||
entryType
|
entryType
|
||||||
downloads
|
downloads
|
||||||
createdAt
|
createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment entryDetails on Entry {
|
||||||
|
id
|
||||||
|
author
|
||||||
|
name
|
||||||
|
summary
|
||||||
|
entryType
|
||||||
|
downloads
|
||||||
|
createdAt
|
||||||
|
description
|
||||||
|
categories {
|
||||||
|
...category
|
||||||
|
}
|
||||||
|
latestRelease {
|
||||||
|
id
|
||||||
|
version
|
||||||
|
downloadSize
|
||||||
|
md5Hash
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
images {
|
||||||
|
...image
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,22 +1,5 @@
|
|||||||
query GetEntryById($id: Long!) {
|
query GetEntryById($id: Long!) {
|
||||||
entry(id: $id) {
|
entry(id: $id) {
|
||||||
id
|
...entryDetails
|
||||||
author
|
|
||||||
name
|
|
||||||
summary
|
|
||||||
entryType
|
|
||||||
downloads
|
|
||||||
createdAt
|
|
||||||
description
|
|
||||||
categories {
|
|
||||||
...category
|
|
||||||
}
|
|
||||||
latestRelease {
|
|
||||||
id
|
|
||||||
version
|
|
||||||
downloadSize
|
|
||||||
md5Hash
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7,7 +7,7 @@ public interface IWorkshopService
|
|||||||
{
|
{
|
||||||
Task<Stream?> GetEntryIcon(long entryId, CancellationToken cancellationToken);
|
Task<Stream?> GetEntryIcon(long entryId, CancellationToken cancellationToken);
|
||||||
Task<ImageUploadResult> SetEntryIcon(long entryId, Progress<StreamProgress> progress, Stream icon, CancellationToken cancellationToken);
|
Task<ImageUploadResult> SetEntryIcon(long entryId, Progress<StreamProgress> progress, Stream icon, CancellationToken cancellationToken);
|
||||||
Task<ImageUploadResult> UploadEntryImage(long entryId, Progress<StreamProgress> progress, Stream image, CancellationToken cancellationToken);
|
Task<ImageUploadResult> UploadEntryImage(long entryId, ImageUploadRequest request, Progress<StreamProgress> progress, CancellationToken cancellationToken);
|
||||||
Task<WorkshopStatus> GetWorkshopStatus(CancellationToken cancellationToken);
|
Task<WorkshopStatus> GetWorkshopStatus(CancellationToken cancellationToken);
|
||||||
Task<bool> ValidateWorkshopStatus(CancellationToken cancellationToken);
|
Task<bool> ValidateWorkshopStatus(CancellationToken cancellationToken);
|
||||||
Task NavigateToEntry(long entryId, EntryType entryType);
|
Task NavigateToEntry(long entryId, EntryType entryType);
|
||||||
|
|||||||
@ -57,19 +57,19 @@ public class WorkshopService : IWorkshopService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<ImageUploadResult> UploadEntryImage(long entryId, Progress<StreamProgress> progress, Stream image, CancellationToken cancellationToken)
|
public async Task<ImageUploadResult> UploadEntryImage(long entryId, ImageUploadRequest request, Progress<StreamProgress> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
image.Seek(0, SeekOrigin.Begin);
|
request.File.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
// Submit the archive
|
// Submit the archive
|
||||||
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
|
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
|
||||||
|
|
||||||
// Construct the request
|
// Construct the request
|
||||||
MultipartFormDataContent content = new();
|
MultipartFormDataContent content = new();
|
||||||
ProgressableStreamContent streamContent = new(image, progress);
|
ProgressableStreamContent streamContent = new(request.File, progress);
|
||||||
streamContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
|
streamContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
|
||||||
content.Add(streamContent, "file", "file.png");
|
content.Add(streamContent, "file", "file.png");
|
||||||
|
|
||||||
// Submit
|
// Submit
|
||||||
HttpResponseMessage response = await client.PostAsync($"entries/{entryId}/image", content, cancellationToken);
|
HttpResponseMessage response = await client.PostAsync($"entries/{entryId}/image", content, cancellationToken);
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
|
|||||||
@ -5,6 +5,8 @@ schema {
|
|||||||
mutation: Mutation
|
mutation: Mutation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
directive @tag(name: String!) on SCHEMA | SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
|
||||||
|
|
||||||
type Category {
|
type Category {
|
||||||
icon: String!
|
icon: String!
|
||||||
id: Long!
|
id: Long!
|
||||||
@ -50,8 +52,13 @@ type Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Image {
|
type Image {
|
||||||
|
description: String
|
||||||
|
height: Int!
|
||||||
id: UUID!
|
id: UUID!
|
||||||
mimeType: String!
|
mimeType: String!
|
||||||
|
name: String!
|
||||||
|
size: Long!
|
||||||
|
width: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
type LayoutInfo {
|
type LayoutInfo {
|
||||||
@ -252,14 +259,39 @@ input EntryTypeOperationFilterInput {
|
|||||||
|
|
||||||
input ImageFilterInput {
|
input ImageFilterInput {
|
||||||
and: [ImageFilterInput!]
|
and: [ImageFilterInput!]
|
||||||
|
description: StringOperationFilterInput
|
||||||
|
height: IntOperationFilterInput
|
||||||
id: UuidOperationFilterInput
|
id: UuidOperationFilterInput
|
||||||
mimeType: StringOperationFilterInput
|
mimeType: StringOperationFilterInput
|
||||||
|
name: StringOperationFilterInput
|
||||||
or: [ImageFilterInput!]
|
or: [ImageFilterInput!]
|
||||||
|
size: LongOperationFilterInput
|
||||||
|
width: IntOperationFilterInput
|
||||||
}
|
}
|
||||||
|
|
||||||
input ImageSortInput {
|
input ImageSortInput {
|
||||||
|
description: SortEnumType
|
||||||
|
height: SortEnumType
|
||||||
id: SortEnumType
|
id: SortEnumType
|
||||||
mimeType: SortEnumType
|
mimeType: SortEnumType
|
||||||
|
name: SortEnumType
|
||||||
|
size: SortEnumType
|
||||||
|
width: SortEnumType
|
||||||
|
}
|
||||||
|
|
||||||
|
input IntOperationFilterInput {
|
||||||
|
eq: Int
|
||||||
|
gt: Int
|
||||||
|
gte: Int
|
||||||
|
in: [Int]
|
||||||
|
lt: Int
|
||||||
|
lte: Int
|
||||||
|
neq: Int
|
||||||
|
ngt: Int
|
||||||
|
ngte: Int
|
||||||
|
nin: [Int]
|
||||||
|
nlt: Int
|
||||||
|
nlte: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
input LayoutInfoFilterInput {
|
input LayoutInfoFilterInput {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user