mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +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());
|
using StreamReader reader = new(metaDataFileEntry.Open());
|
||||||
PluginInfo pluginInfo = CoreJson.DeserializeObject<PluginInfo>(reader.ReadToEnd())!;
|
PluginInfo pluginInfo = CoreJson.DeserializeObject<PluginInfo>(reader.ReadToEnd())!;
|
||||||
if (!pluginInfo.Main.EndsWith(".dll"))
|
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);
|
Plugin? existing = _plugins.FirstOrDefault(p => p.Guid == pluginInfo.Guid);
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
|
|||||||
@ -35,4 +35,24 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AvaloniaResource Include="Assets\**" />
|
<AvaloniaResource Include="Assets\**" />
|
||||||
</ItemGroup>
|
</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>
|
</Project>
|
||||||
@ -27,6 +27,7 @@ public class LayoutListViewModel : List.EntryListViewModel
|
|||||||
And = new[]
|
And = new[]
|
||||||
{
|
{
|
||||||
new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = EntryType.Layout}},
|
new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = EntryType.Layout}},
|
||||||
|
new EntryFilterInput(){LatestReleaseId = new LongOperationFilterInput {Gt = 0}},
|
||||||
base.GetFilter()
|
base.GetFilter()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
|
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.Screens.Workshop.SubmissionWizard.Steps.Profile;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
@ -56,7 +57,9 @@ public class SubmissionWizardState : IDisposable
|
|||||||
|
|
||||||
public void StartForCurrentEntry()
|
public void StartForCurrentEntry()
|
||||||
{
|
{
|
||||||
if (EntryType == EntryType.Profile)
|
if (EntryType == EntryType.Plugin)
|
||||||
|
ChangeScreen<PluginSelectionStepViewModel>();
|
||||||
|
else if (EntryType == EntryType.Profile)
|
||||||
ChangeScreen<ProfileSelectionStepViewModel>();
|
ChangeScreen<ProfileSelectionStepViewModel>();
|
||||||
else if (EntryType == EntryType.Layout)
|
else if (EntryType == EntryType.Layout)
|
||||||
ChangeScreen<LayoutSelectionStepViewModel>();
|
ChangeScreen<LayoutSelectionStepViewModel>();
|
||||||
|
|||||||
@ -44,5 +44,14 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</RadioButton.Content>
|
</RadioButton.Content>
|
||||||
</RadioButton>
|
</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>
|
</StackPanel>
|
||||||
</UserControl>
|
</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.Extensions;
|
||||||
using Artemis.UI.Screens.Workshop.Entries;
|
using Artemis.UI.Screens.Workshop.Entries;
|
||||||
using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout;
|
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.Screens.Workshop.SubmissionWizard.Steps.Profile;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
@ -37,20 +38,15 @@ public partial class SpecificationsStepViewModel : SubmissionViewModel
|
|||||||
// Apply what's there so far
|
// Apply what's there so far
|
||||||
ApplyToState();
|
ApplyToState();
|
||||||
|
|
||||||
switch (State.EntryType)
|
if (State.EntryType == EntryType.Layout)
|
||||||
{
|
|
||||||
case EntryType.Layout:
|
|
||||||
State.ChangeScreen<LayoutInfoStepViewModel>();
|
State.ChangeScreen<LayoutInfoStepViewModel>();
|
||||||
break;
|
else if (State.EntryType == EntryType.Plugin)
|
||||||
case EntryType.Plugin:
|
State.ChangeScreen<PluginSelectionStepViewModel>();
|
||||||
break;
|
else if (State.EntryType == EntryType.Profile)
|
||||||
case EntryType.Profile:
|
|
||||||
State.ChangeScreen<ProfileAdaptionHintsStepViewModel>();
|
State.ChangeScreen<ProfileAdaptionHintsStepViewModel>();
|
||||||
break;
|
else
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void ExecuteContinue()
|
private void ExecuteContinue()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -15,6 +15,7 @@ public class EntryUploadHandlerFactory
|
|||||||
{
|
{
|
||||||
return entryType switch
|
return entryType switch
|
||||||
{
|
{
|
||||||
|
EntryType.Plugin => _container.Resolve<PluginEntryUploadHandler>(),
|
||||||
EntryType.Profile => _container.Resolve<ProfileEntryUploadHandler>(),
|
EntryType.Profile => _container.Resolve<ProfileEntryUploadHandler>(),
|
||||||
EntryType.Layout => _container.Resolve<LayoutEntryUploadHandler>(),
|
EntryType.Layout => _container.Resolve<LayoutEntryUploadHandler>(),
|
||||||
_ => throw new NotSupportedException($"EntryType '{entryType}' is not supported.")
|
_ => 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