mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-12 21:38:38 +00:00
Workshop - Added plugin upload support
This commit is contained in:
parent
38a1318170
commit
d9df443970
@ -597,7 +597,7 @@ internal class PluginManagementService : IPluginManagementService
|
||||
using StreamReader reader = new(metaDataFileEntry.Open());
|
||||
PluginInfo pluginInfo = CoreJson.DeserializeObject<PluginInfo>(reader.ReadToEnd())!;
|
||||
if (!pluginInfo.Main.EndsWith(".dll"))
|
||||
throw new ArtemisPluginException("Main entry in plugin.json must point to a .dll file" + fileName);
|
||||
throw new ArtemisPluginException("Main entry in plugin.json must point to a .dll file");
|
||||
|
||||
Plugin? existing = _plugins.FirstOrDefault(p => p.Guid == pluginInfo.Guid);
|
||||
if (existing != null)
|
||||
|
||||
@ -35,4 +35,24 @@
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Screens\Workshop\SubmissionWizard\Steps\Plugin\PluginSelectionStepView.axaml.cs">
|
||||
<DependentUpon>PluginSelectionStepView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Screens\Workshop\SubmissionWizard\Steps\Plugin\PluginAdaptionHintsStepView.axaml.cs">
|
||||
<DependentUpon>ProfileAdaptionHintsStepView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Screens\Workshop\SubmissionWizard\Steps\Plugin\PluginSelectionStepView.axaml.cs">
|
||||
<DependentUpon>ProfileSelectionStepView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="Screens\Workshop\SubmissionWizard\Steps\Plugin\PluginAdaptionHintsStepView.axaml" />
|
||||
<AdditionalFiles Include="Screens\Workshop\SubmissionWizard\Steps\Plugin\PluginSelectionStepView.axaml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -27,6 +27,7 @@ public class LayoutListViewModel : List.EntryListViewModel
|
||||
And = new[]
|
||||
{
|
||||
new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = EntryType.Layout}},
|
||||
new EntryFilterInput(){LatestReleaseId = new LongOperationFilterInput {Gt = 0}},
|
||||
base.GetFilter()
|
||||
}
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
|
||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Plugin;
|
||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.WebClient.Workshop;
|
||||
@ -56,7 +57,9 @@ public class SubmissionWizardState : IDisposable
|
||||
|
||||
public void StartForCurrentEntry()
|
||||
{
|
||||
if (EntryType == EntryType.Profile)
|
||||
if (EntryType == EntryType.Plugin)
|
||||
ChangeScreen<PluginSelectionStepViewModel>();
|
||||
else if (EntryType == EntryType.Profile)
|
||||
ChangeScreen<ProfileSelectionStepViewModel>();
|
||||
else if (EntryType == EntryType.Layout)
|
||||
ChangeScreen<LayoutSelectionStepViewModel>();
|
||||
|
||||
@ -44,5 +44,14 @@
|
||||
</StackPanel>
|
||||
</RadioButton.Content>
|
||||
</RadioButton>
|
||||
<RadioButton GroupName="EntryType"
|
||||
IsChecked="{CompiledBinding SelectedEntryType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static workshop:EntryType.Plugin}}">
|
||||
<RadioButton.Content>
|
||||
<StackPanel>
|
||||
<TextBlock>Plugin</TextBlock>
|
||||
<TextBlock Classes="subtitle" TextWrapping="Wrap">A plugin that adds new features to Artemis.</TextBlock>
|
||||
</StackPanel>
|
||||
</RadioButton.Content>
|
||||
</RadioButton>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
<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:plugin="clr-namespace:Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Plugin"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Plugin.PluginSelectionStepView"
|
||||
x:DataType="plugin:PluginSelectionStepViewModel">
|
||||
<Grid RowDefinitions="Auto,Auto">
|
||||
<StackPanel>
|
||||
<StackPanel.Styles>
|
||||
<Styles>
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="TextWrapping" Value="Wrap"></Setter>
|
||||
</Style>
|
||||
</Styles>
|
||||
</StackPanel.Styles>
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}" TextWrapping="Wrap">
|
||||
Plugin selection
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Please select the plugin you want to share, a preview will be shown below.
|
||||
</TextBlock>
|
||||
<Button Command="{CompiledBinding Browse}" Margin="0 20">Browse file</Button>
|
||||
</StackPanel>
|
||||
<Border Grid.Row="1" Classes="card" ClipToBounds="True" IsVisible="{CompiledBinding SelectedPlugin, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<Grid RowDefinitions="30,30,30,30,Auto" ColumnDefinitions="200,Auto">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" FontWeight="SemiBold">Path</TextBlock>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="{CompiledBinding Path}"></TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" FontWeight="SemiBold">Name</TextBlock>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="{CompiledBinding SelectedPlugin.Name}"></TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" FontWeight="SemiBold">Description</TextBlock>
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" Text="{CompiledBinding SelectedPlugin.Description}"></TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" FontWeight="SemiBold">Main entry point</TextBlock>
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="{CompiledBinding SelectedPlugin.Main}"></TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" FontWeight="SemiBold">Version</TextBlock>
|
||||
<TextBlock Grid.Row="4" Grid.Column="1" Text="{CompiledBinding SelectedPlugin.Version}"></TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@ -0,0 +1,11 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Plugin;
|
||||
|
||||
public partial class PluginSelectionStepView : ReactiveUserControl<PluginSelectionStepViewModel>
|
||||
{
|
||||
public PluginSelectionStepView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Plugin;
|
||||
|
||||
public partial class PluginSelectionStepViewModel : SubmissionViewModel
|
||||
{
|
||||
private readonly IWindowService _windowService;
|
||||
[Notify] private PluginInfo? _selectedPlugin;
|
||||
[Notify] private string? _path;
|
||||
|
||||
/// <inheritdoc />
|
||||
public PluginSelectionStepViewModel(IWindowService windowService)
|
||||
{
|
||||
_windowService = windowService;
|
||||
|
||||
GoBack = ReactiveCommand.Create(() => State.ChangeScreen<EntryTypeStepViewModel>());
|
||||
Continue = ReactiveCommand.Create(ExecuteContinue, this.WhenAnyValue(vm => vm.SelectedPlugin).Select(p => p != null));
|
||||
Browse = ReactiveCommand.CreateFromTask(ExecuteBrowse);
|
||||
|
||||
this.WhenActivated((CompositeDisposable _) =>
|
||||
{
|
||||
ShowGoBack = State.EntryId == null;
|
||||
if (State.EntrySource is PluginEntrySource pluginEntrySource)
|
||||
{
|
||||
SelectedPlugin = pluginEntrySource.PluginInfo;
|
||||
Path = pluginEntrySource.Path;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> Browse { get; }
|
||||
|
||||
private async Task ExecuteBrowse()
|
||||
{
|
||||
string[]? files = await _windowService.CreateOpenFileDialog().HavingFilter(f => f.WithExtension("zip").WithName("ZIP files")).ShowAsync();
|
||||
if (files == null)
|
||||
return;
|
||||
|
||||
// Find the metadata file in the zip
|
||||
using ZipArchive archive = ZipFile.OpenRead(files[0]);
|
||||
ZipArchiveEntry? metaDataFileEntry = archive.Entries.FirstOrDefault(e => e.Name == "plugin.json");
|
||||
if (metaDataFileEntry == null)
|
||||
throw new ArtemisPluginException("Couldn't find a plugin.json in " + files[0]);
|
||||
|
||||
using StreamReader reader = new(metaDataFileEntry.Open());
|
||||
PluginInfo pluginInfo = CoreJson.DeserializeObject<PluginInfo>(reader.ReadToEnd())!;
|
||||
if (!pluginInfo.Main.EndsWith(".dll"))
|
||||
throw new ArtemisPluginException("Main entry in plugin.json must point to a .dll file");
|
||||
|
||||
SelectedPlugin = pluginInfo;
|
||||
Path = files[0];
|
||||
}
|
||||
|
||||
private void ExecuteContinue()
|
||||
{
|
||||
if (SelectedPlugin == null || Path == null)
|
||||
return;
|
||||
|
||||
State.EntrySource = new PluginEntrySource(SelectedPlugin, Path);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(State.Name))
|
||||
State.Name = SelectedPlugin.Name;
|
||||
if (string.IsNullOrWhiteSpace(State.Summary))
|
||||
State.Summary = SelectedPlugin.Description ?? "";
|
||||
|
||||
if (State.EntryId == null)
|
||||
State.ChangeScreen<SpecificationsStepViewModel>();
|
||||
else
|
||||
State.ChangeScreen<UploadStepViewModel>();
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ using System.Reactive.Disposables;
|
||||
using Artemis.UI.Extensions;
|
||||
using Artemis.UI.Screens.Workshop.Entries;
|
||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
|
||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Plugin;
|
||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using DynamicData;
|
||||
@ -37,19 +38,14 @@ public partial class SpecificationsStepViewModel : SubmissionViewModel
|
||||
// Apply what's there so far
|
||||
ApplyToState();
|
||||
|
||||
switch (State.EntryType)
|
||||
{
|
||||
case EntryType.Layout:
|
||||
State.ChangeScreen<LayoutInfoStepViewModel>();
|
||||
break;
|
||||
case EntryType.Plugin:
|
||||
break;
|
||||
case EntryType.Profile:
|
||||
State.ChangeScreen<ProfileAdaptionHintsStepViewModel>();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
if (State.EntryType == EntryType.Layout)
|
||||
State.ChangeScreen<LayoutInfoStepViewModel>();
|
||||
else if (State.EntryType == EntryType.Plugin)
|
||||
State.ChangeScreen<PluginSelectionStepViewModel>();
|
||||
else if (State.EntryType == EntryType.Profile)
|
||||
State.ChangeScreen<ProfileAdaptionHintsStepViewModel>();
|
||||
else
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
private void ExecuteContinue()
|
||||
|
||||
@ -15,6 +15,7 @@ public class EntryUploadHandlerFactory
|
||||
{
|
||||
return entryType switch
|
||||
{
|
||||
EntryType.Plugin => _container.Resolve<PluginEntryUploadHandler>(),
|
||||
EntryType.Profile => _container.Resolve<ProfileEntryUploadHandler>(),
|
||||
EntryType.Layout => _container.Resolve<LayoutEntryUploadHandler>(),
|
||||
_ => throw new NotSupportedException($"EntryType '{entryType}' is not supported.")
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
||||
|
||||
public class PluginEntrySource : IEntrySource
|
||||
{
|
||||
public PluginEntrySource(PluginInfo pluginInfo, string path)
|
||||
{
|
||||
PluginInfo = pluginInfo;
|
||||
Path = path;
|
||||
}
|
||||
|
||||
public PluginInfo PluginInfo { get; set; }
|
||||
public string Path { get; set; }
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
using System.Net.Http.Headers;
|
||||
using Artemis.WebClient.Workshop.Entities;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
||||
|
||||
public class PluginEntryUploadHandler : IEntryUploadHandler
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public PluginEntryUploadHandler(IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<EntryUploadResult> CreateReleaseAsync(long entryId, IEntrySource entrySource, CancellationToken cancellationToken)
|
||||
{
|
||||
if (entrySource is not PluginEntrySource source)
|
||||
throw new InvalidOperationException("Can only create releases for plugins");
|
||||
|
||||
// Submit the archive
|
||||
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
|
||||
|
||||
// Construct the request
|
||||
await using FileStream fileStream = File.Open(source.Path, FileMode.Open);
|
||||
MultipartFormDataContent content = new();
|
||||
StreamContent streamContent = new(fileStream);
|
||||
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");
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user