mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-12 13:28:33 +00:00
Workshop - Added release management
This commit is contained in:
parent
7030c7af2a
commit
cac44d748d
@ -49,7 +49,9 @@ namespace Artemis.UI.Routing
|
||||
new RouteRegistration<WorkshopLibraryViewModel>("library", [
|
||||
new RouteRegistration<InstalledTabViewModel>("installed"),
|
||||
new RouteRegistration<SubmissionsTabViewModel>("submissions"),
|
||||
new RouteRegistration<SubmissionDetailViewModel>("submissions/{entryId:long}")
|
||||
new RouteRegistration<SubmissionManagementViewModel>("submissions/{entryId:long}", [
|
||||
new RouteRegistration<SubmissionReleaseViewModel>("releases/{releaseId:long}")
|
||||
])
|
||||
])
|
||||
]),
|
||||
new RouteRegistration<SurfaceEditorViewModel>("surface-editor"),
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Artemis.WebClient.Workshop.Models;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
|
||||
@ -24,7 +22,6 @@ public partial class EntryReleasesViewModel : ActivatableViewModelBase
|
||||
|
||||
Entry = entry;
|
||||
Releases = Entry.Releases.OrderByDescending(r => r.CreatedAt).Take(5).Select(r => getEntryReleaseItemViewModel(r)).ToList();
|
||||
NavigateToRelease = ReactiveCommand.CreateFromTask<IRelease>(ExecuteNavigateToRelease);
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
@ -35,30 +32,11 @@ public partial class EntryReleasesViewModel : ActivatableViewModelBase
|
||||
|
||||
this.WhenAnyValue(vm => vm.SelectedRelease)
|
||||
.WhereNotNull()
|
||||
.Subscribe(s => ExecuteNavigateToRelease(s.Release))
|
||||
.Subscribe(s => _router.Navigate($"/releases/{s.Release.Id}"))
|
||||
.DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
public IEntryDetails Entry { get; }
|
||||
public List<EntryReleaseItemViewModel> Releases { get; }
|
||||
public ReactiveCommand<IRelease, Unit> NavigateToRelease { get; }
|
||||
|
||||
private async Task ExecuteNavigateToRelease(IRelease release)
|
||||
{
|
||||
switch (Entry.EntryType)
|
||||
{
|
||||
case EntryType.Profile:
|
||||
await _router.Navigate($"workshop/entries/profiles/details/{Entry.Id}/releases/{release.Id}");
|
||||
break;
|
||||
case EntryType.Layout:
|
||||
await _router.Navigate($"workshop/entries/layouts/details/{Entry.Id}/releases/{release.Id}");
|
||||
break;
|
||||
case EntryType.Plugin:
|
||||
await _router.Navigate($"workshop/entries/plugins/details/{Entry.Id}/releases/{release.Id}");
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(Entry.EntryType));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,85 +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"
|
||||
xmlns:library="clr-namespace:Artemis.UI.Screens.Workshop.Library"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Library.SubmissionDetailView"
|
||||
x:DataType="library:SubmissionDetailViewModel">
|
||||
<UserControl.Resources>
|
||||
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid ColumnDefinitions="300,*,300" RowDefinitions="*, Auto">
|
||||
<StackPanel Grid.Column="0" Grid.RowSpan="2" Spacing="10">
|
||||
<Border Classes="card" VerticalAlignment="Top" Margin="0 0 10 0">
|
||||
<StackPanel>
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Management</TextBlock>
|
||||
<Border Classes="card-separator" />
|
||||
|
||||
<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>
|
||||
|
||||
<Border Classes="card-separator" />
|
||||
|
||||
<StackPanel Spacing="5">
|
||||
<Button HorizontalAlignment="Stretch" Command="{CompiledBinding CreateRelease}">
|
||||
Create new release
|
||||
</Button>
|
||||
<Button Classes="danger" HorizontalAlignment="Stretch" Command="{CompiledBinding DeleteSubmission}">
|
||||
Delete submission
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<controls:HyperlinkButton Command="{CompiledBinding ViewWorkshopPage}" HorizontalAlignment="Center">
|
||||
View workshop page
|
||||
</controls:HyperlinkButton>
|
||||
</StackPanel>
|
||||
|
||||
<ContentControl Grid.Column="1" Grid.Row="0" Content="{CompiledBinding EntrySpecificationsViewModel}"></ContentControl>
|
||||
|
||||
<Border Grid.Column="2" Grid.Row="0" Classes="card" Margin="10 0 0 0">
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
<ScrollViewer Grid.Row="0" Classes="with-padding" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl ItemsSource="{CompiledBinding Images}">
|
||||
<ItemsControl.Styles>
|
||||
<Styles>
|
||||
<Style Selector="ItemsControl > ContentPresenter">
|
||||
<Setter Property="Margin" Value="0 0 0 10"></Setter>
|
||||
</Style>
|
||||
<Style Selector="ItemsControl > ContentPresenter:nth-last-child(1)">
|
||||
<Setter Property="Margin" Value="0 0 0 0"></Setter>
|
||||
</Style>
|
||||
</Styles>
|
||||
</ItemsControl.Styles>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
<Button Grid.Row="1" HorizontalAlignment="Stretch" Command="{CompiledBinding AddImage}">Add image</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<StackPanel Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="1" HorizontalAlignment="Right" Spacing="5" Orientation="Horizontal" Margin="0 10 0 0">
|
||||
<Button Command="{CompiledBinding DiscardChanges}">Discard changes</Button>
|
||||
<Button Command="{CompiledBinding SaveChanges}">Save</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@ -1,11 +0,0 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Library;
|
||||
|
||||
public partial class SubmissionDetailView : ReactiveUserControl<SubmissionDetailViewModel>
|
||||
{
|
||||
public SubmissionDetailView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
<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:library="clr-namespace:Artemis.UI.Screens.Workshop.Library"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Library.SubmissionDetailsView"
|
||||
x:DataType="library:SubmissionDetailsViewModel">
|
||||
<Grid ColumnDefinitions="*,300" RowDefinitions="*, Auto">
|
||||
<ContentControl Grid.Column="0" Grid.Row="0" Content="{CompiledBinding EntrySpecificationsViewModel}"></ContentControl>
|
||||
|
||||
<Border Grid.Column="1" Grid.Row="0" Classes="card" Margin="10 0 0 0">
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
<ScrollViewer Grid.Row="0" Classes="with-padding" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl ItemsSource="{CompiledBinding Images}">
|
||||
<ItemsControl.Styles>
|
||||
<Styles>
|
||||
<Style Selector="ItemsControl > ContentPresenter">
|
||||
<Setter Property="Margin" Value="0 0 0 10"></Setter>
|
||||
</Style>
|
||||
<Style Selector="ItemsControl > ContentPresenter:nth-last-child(1)">
|
||||
<Setter Property="Margin" Value="0 0 0 0"></Setter>
|
||||
</Style>
|
||||
</Styles>
|
||||
</ItemsControl.Styles>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
<Button Grid.Row="1" HorizontalAlignment="Stretch" Command="{CompiledBinding AddImage}">Add image</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" HorizontalAlignment="Right" Spacing="5" Orientation="Horizontal" Margin="0 10 0 0">
|
||||
<Button Command="{CompiledBinding DiscardChanges}">Discard changes</Button>
|
||||
<Button Command="{CompiledBinding SaveChanges}">Save</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -0,0 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Library;
|
||||
|
||||
public partial class SubmissionDetailsView : ReactiveUserControl<SubmissionDetailsViewModel>
|
||||
{
|
||||
public SubmissionDetailsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -8,8 +8,7 @@ using System.Reactive;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Screens.Workshop.Image;
|
||||
using Artemis.UI.Screens.Workshop.Parameters;
|
||||
using Artemis.UI.Screens.Workshop.SubmissionWizard;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.WebClient.Workshop;
|
||||
@ -25,7 +24,7 @@ using EntrySpecificationsViewModel = Artemis.UI.Screens.Workshop.Entries.Details
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Library;
|
||||
|
||||
public partial class SubmissionDetailViewModel : RoutableScreen<WorkshopDetailParameters>
|
||||
public partial class SubmissionDetailsViewModel : RoutableScreen
|
||||
{
|
||||
private readonly IWorkshopClient _client;
|
||||
private readonly IWindowService _windowService;
|
||||
@ -40,7 +39,7 @@ public partial class SubmissionDetailViewModel : RoutableScreen<WorkshopDetailPa
|
||||
[Notify] private EntrySpecificationsViewModel? _entrySpecificationsViewModel;
|
||||
[Notify(Setter.Private)] private bool _hasChanges;
|
||||
|
||||
public SubmissionDetailViewModel(IWorkshopClient client,
|
||||
public SubmissionDetailsViewModel(IWorkshopClient client,
|
||||
IWindowService windowService,
|
||||
IWorkshopService workshopService,
|
||||
IRouter router,
|
||||
@ -56,34 +55,23 @@ public partial class SubmissionDetailViewModel : RoutableScreen<WorkshopDetailPa
|
||||
_getExistingImageSubmissionViewModel = getExistingImageSubmissionViewModel;
|
||||
_getImageSubmissionViewModel = getImageSubmissionViewModel;
|
||||
|
||||
CreateRelease = ReactiveCommand.CreateFromTask(ExecuteCreateRelease);
|
||||
DeleteSubmission = ReactiveCommand.CreateFromTask(ExecuteDeleteSubmission);
|
||||
ViewWorkshopPage = ReactiveCommand.CreateFromTask(ExecuteViewWorkshopPage);
|
||||
AddImage = ReactiveCommand.CreateFromTask(ExecuteAddImage);
|
||||
DiscardChanges = ReactiveCommand.CreateFromTask(ExecuteDiscardChanges, this.WhenAnyValue(vm => vm.HasChanges));
|
||||
SaveChanges = ReactiveCommand.CreateFromTask(ExecuteSaveChanges, this.WhenAnyValue(vm => vm.HasChanges));
|
||||
}
|
||||
|
||||
|
||||
public ObservableCollection<ImageSubmissionViewModel> Images { get; } = new();
|
||||
public ReactiveCommand<Unit, Unit> CreateRelease { get; }
|
||||
public ReactiveCommand<Unit, Unit> DeleteSubmission { get; }
|
||||
public ReactiveCommand<Unit, Unit> ViewWorkshopPage { get; }
|
||||
public ReactiveCommand<Unit, Unit> AddImage { get; }
|
||||
public ReactiveCommand<Unit, Unit> SaveChanges { get; }
|
||||
public ReactiveCommand<Unit, Unit> DiscardChanges { get; }
|
||||
|
||||
public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
|
||||
public async Task SetEntry(IGetSubmittedEntryById_Entry? entry, CancellationToken cancellationToken)
|
||||
{
|
||||
IOperationResult<IGetSubmittedEntryByIdResult> result = await _client.GetSubmittedEntryById.ExecuteAsync(parameters.EntryId, cancellationToken);
|
||||
if (result.IsErrorResult())
|
||||
return;
|
||||
|
||||
Entry = result.Data?.Entry;
|
||||
Entry = entry;
|
||||
await ApplyDetailsFromEntry(cancellationToken);
|
||||
ApplyImagesFromEntry();
|
||||
}
|
||||
|
||||
public override async Task OnClosing(NavigationArguments args)
|
||||
public async Task OnClosing(NavigationArguments args)
|
||||
{
|
||||
if (!HasChanges)
|
||||
return;
|
||||
@ -243,30 +231,7 @@ public partial class SubmissionDetailViewModel : RoutableScreen<WorkshopDetailPa
|
||||
HasChanges = false;
|
||||
await _router.Reload();
|
||||
}
|
||||
|
||||
private async Task ExecuteCreateRelease(CancellationToken cancellationToken)
|
||||
{
|
||||
if (Entry != null)
|
||||
await _windowService.ShowDialogAsync<ReleaseWizardViewModel>(Entry);
|
||||
}
|
||||
|
||||
private async Task ExecuteDeleteSubmission(CancellationToken cancellationToken)
|
||||
{
|
||||
if (Entry == null)
|
||||
return;
|
||||
|
||||
bool confirmed = await _windowService.ShowConfirmContentDialog(
|
||||
"Delete submission?",
|
||||
"You cannot undo this by yourself.\r\n" +
|
||||
"Users that have already downloaded your submission will keep it.");
|
||||
if (!confirmed)
|
||||
return;
|
||||
|
||||
IOperationResult<IRemoveEntryResult> result = await _client.RemoveEntry.ExecuteAsync(Entry.Id, cancellationToken);
|
||||
result.EnsureNoErrors();
|
||||
await _router.Navigate("workshop/library/submissions");
|
||||
}
|
||||
|
||||
|
||||
private async Task ExecuteAddImage(CancellationToken arg)
|
||||
{
|
||||
string[]? result = await _windowService.CreateOpenFileDialog().WithAllowMultiple().HavingFilter(f => f.WithBitmaps()).ShowAsync();
|
||||
@ -297,12 +262,6 @@ public partial class SubmissionDetailViewModel : RoutableScreen<WorkshopDetailPa
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteViewWorkshopPage()
|
||||
{
|
||||
if (Entry != null)
|
||||
await _workshopService.NavigateToEntry(Entry.Id, Entry.EntryType);
|
||||
}
|
||||
|
||||
private void InputChanged(object? sender, EventArgs e)
|
||||
{
|
||||
UpdateHasChanges();
|
||||
@ -0,0 +1,80 @@
|
||||
<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:library="clr-namespace:Artemis.UI.Screens.Workshop.Library"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:ui="clr-namespace:Artemis.UI"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Library.SubmissionManagementView"
|
||||
x:DataType="library:SubmissionManagementViewModel">
|
||||
<UserControl.Resources>
|
||||
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid ColumnDefinitions="300,*" RowDefinitions="*, Auto">
|
||||
<StackPanel Grid.Column="0" Grid.Row="0" Spacing="10" Margin="0 0 10 0">
|
||||
<Border Classes="card" VerticalAlignment="Top">
|
||||
<StackPanel>
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Management</TextBlock>
|
||||
<Border Classes="card-separator" />
|
||||
|
||||
<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>
|
||||
|
||||
<Border Classes="card-separator" />
|
||||
|
||||
<StackPanel Spacing="5">
|
||||
<Button HorizontalAlignment="Stretch" Command="{CompiledBinding CreateRelease}">
|
||||
Create new release
|
||||
</Button>
|
||||
<Button Classes="danger" HorizontalAlignment="Stretch" Command="{CompiledBinding DeleteSubmission}">
|
||||
Delete submission
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border Classes="card" IsVisible="{CompiledBinding Releases.Count}">
|
||||
<StackPanel>
|
||||
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Releases</TextBlock>
|
||||
<Border Classes="card-separator" />
|
||||
|
||||
<ListBox ItemsSource="{CompiledBinding Releases}" SelectedItem="{CompiledBinding SelectedRelease}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Margin="0 5">
|
||||
<TextBlock Text="{CompiledBinding Version}"></TextBlock>
|
||||
<TextBlock Classes="subtitle" ToolTip.Tip="{CompiledBinding CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
||||
<avalonia:MaterialIcon Kind="Calendar" />
|
||||
<Run>Created</Run>
|
||||
<Run Text="{CompiledBinding CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<controls:HyperlinkButton Command="{CompiledBinding ViewWorkshopPage}" HorizontalAlignment="Center">
|
||||
View workshop page
|
||||
</controls:HyperlinkButton>
|
||||
</StackPanel>
|
||||
|
||||
<controls:Frame Grid.Column="1" Grid.Row="0" Name="RouterFrame" IsNavigationStackEnabled="False" CacheSize="0">
|
||||
<controls:Frame.NavigationPageFactory>
|
||||
<ui:PageFactory />
|
||||
</controls:Frame.NavigationPageFactory>
|
||||
</controls:Frame>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Library;
|
||||
|
||||
public partial class SubmissionManagementView : ReactiveUserControl<SubmissionManagementViewModel>
|
||||
{
|
||||
public SubmissionManagementView()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.Screen)
|
||||
.Subscribe(screen => RouterFrame.NavigateFromObject(screen ?? ViewModel?.DetailsViewModel))
|
||||
.DisposeWith(d));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Screens.Workshop.Parameters;
|
||||
using Artemis.UI.Screens.Workshop.SubmissionWizard;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
using StrawberryShake;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Library;
|
||||
|
||||
public partial class SubmissionManagementViewModel : RoutableHostScreen<RoutableScreen, WorkshopDetailParameters>
|
||||
{
|
||||
private readonly IWorkshopClient _client;
|
||||
private readonly IWindowService _windowService;
|
||||
private readonly IRouter _router;
|
||||
private readonly IWorkshopService _workshopService;
|
||||
|
||||
[Notify] private IGetSubmittedEntryById_Entry? _entry;
|
||||
[Notify] private List<IGetSubmittedEntryById_Entry_Releases>? _releases;
|
||||
[Notify] private IGetSubmittedEntryById_Entry_Releases? _selectedRelease;
|
||||
|
||||
public SubmissionManagementViewModel(IWorkshopClient client, IRouter router, IWindowService windowService, IWorkshopService workshopService, SubmissionDetailsViewModel detailsViewModel)
|
||||
{
|
||||
DetailsViewModel = detailsViewModel;
|
||||
_client = client;
|
||||
_router = router;
|
||||
_windowService = windowService;
|
||||
_workshopService = workshopService;
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
this.WhenAnyValue(vm => vm.SelectedRelease)
|
||||
.WhereNotNull()
|
||||
.Subscribe(r => _router.Navigate($"/releases/{r.Id}"))
|
||||
.DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
public SubmissionDetailsViewModel DetailsViewModel { get; }
|
||||
|
||||
public async Task ViewWorkshopPage()
|
||||
{
|
||||
if (Entry != null)
|
||||
await _workshopService.NavigateToEntry(Entry.Id, Entry.EntryType);
|
||||
}
|
||||
|
||||
public async Task CreateRelease()
|
||||
{
|
||||
if (Entry != null)
|
||||
await _windowService.ShowDialogAsync<ReleaseWizardViewModel>(Entry);
|
||||
}
|
||||
|
||||
public async Task DeleteSubmission()
|
||||
{
|
||||
if (Entry == null)
|
||||
return;
|
||||
|
||||
bool confirmed = await _windowService.ShowConfirmContentDialog(
|
||||
"Delete submission?",
|
||||
"You cannot undo this by yourself.\r\n" +
|
||||
"Users that have already downloaded your submission will keep it.");
|
||||
if (!confirmed)
|
||||
return;
|
||||
|
||||
IOperationResult<IRemoveEntryResult> result = await _client.RemoveEntry.ExecuteAsync(Entry.Id);
|
||||
result.EnsureNoErrors();
|
||||
await _router.Navigate("workshop/library/submissions");
|
||||
}
|
||||
|
||||
public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
|
||||
{
|
||||
// If there is a 2nd parameter, it's a release ID
|
||||
SelectedRelease = args.RouteParameters.Length > 1 ? Releases?.FirstOrDefault(r => r.Id == (long) args.RouteParameters[1]) : null;
|
||||
|
||||
IOperationResult<IGetSubmittedEntryByIdResult> result = await _client.GetSubmittedEntryById.ExecuteAsync(parameters.EntryId, cancellationToken);
|
||||
if (result.IsErrorResult())
|
||||
return;
|
||||
|
||||
Entry = result.Data?.Entry;
|
||||
Releases = Entry?.Releases.OrderByDescending(r => r.CreatedAt).ToList();
|
||||
|
||||
await DetailsViewModel.SetEntry(Entry, cancellationToken);
|
||||
}
|
||||
|
||||
public override async Task OnClosing(NavigationArguments args)
|
||||
{
|
||||
await DetailsViewModel.OnClosing(args);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
<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:library="clr-namespace:Artemis.UI.Screens.Workshop.Library"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Library.SubmissionReleaseView"
|
||||
x:DataType="library:SubmissionReleaseViewModel">
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="5">
|
||||
<Button VerticalAlignment="Center" Classes="icon-button" Command="{CompiledBinding Close}">
|
||||
<avalonia:MaterialIcon Kind="ArrowBack" />
|
||||
</Button>
|
||||
<TextBlock Classes="h3 no-margin" Text="{CompiledBinding Release.Version}" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid Row="1" ColumnDefinitions="Auto,*">
|
||||
<Label Grid.Column="0" Target="DescriptionEditor" Margin="0 28 0 0">Changelog</Label>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<CheckBox Name="SynchronizedScrolling" IsChecked="True" VerticalAlignment="Bottom">Synchronized scrolling</CheckBox>
|
||||
<controls:HyperlinkButton
|
||||
Margin="0 0 0 -20"
|
||||
Content="Markdown supported"
|
||||
NavigateUri="https://wiki.artemis-rgb.com/guides/user/markdown?mtm_campaign=artemis&mtm_kwd=markdown-editor"
|
||||
HorizontalAlignment="Right" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="2" Grid.Column="0" ColumnDefinitions="*,Auto,*">
|
||||
<Border Grid.Column="0" BorderThickness="1"
|
||||
BorderBrush="{DynamicResource TextControlBorderBrush}"
|
||||
CornerRadius="{DynamicResource ControlCornerRadius}"
|
||||
Background="{DynamicResource TextControlBackground}"
|
||||
Padding="{DynamicResource TextControlThemePadding}">
|
||||
<avaloniaEdit:TextEditor
|
||||
FontFamily="{StaticResource RobotoMono}"
|
||||
FontSize="13"
|
||||
Name="DescriptionEditor"
|
||||
Document="{CompiledBinding MarkdownDocument}"
|
||||
WordWrap="True" />
|
||||
</Border>
|
||||
|
||||
<GridSplitter Grid.Column="1" Margin="5 0"></GridSplitter>
|
||||
<Border Grid.Column="2" Classes="card-condensed">
|
||||
<mdxaml:MarkdownScrollViewer Margin="5 0"
|
||||
Name="DescriptionPreview"
|
||||
Markdown="{CompiledBinding Changelog}"
|
||||
MarkdownStyleName="FluentAvalonia"
|
||||
SaveScrollValueWhenContentUpdated="True">
|
||||
<mdxaml:MarkdownScrollViewer.Styles>
|
||||
<StyleInclude Source="/Styles/Markdown.axaml" />
|
||||
</mdxaml:MarkdownScrollViewer.Styles>
|
||||
</mdxaml:MarkdownScrollViewer>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="3" Margin="0 10 0 0" Orientation="Horizontal" Spacing="5" HorizontalAlignment="Right">
|
||||
<Button Classes="danger" Command="{CompiledBinding DeleteRelease}">Delete release</Button>
|
||||
<Button Command="{CompiledBinding Discard}">Discard changes</Button>
|
||||
<Button Command="{CompiledBinding Save}">Save</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@ -0,0 +1,100 @@
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Immutable;
|
||||
using Avalonia.ReactiveUI;
|
||||
using AvaloniaEdit.TextMate;
|
||||
using ReactiveUI;
|
||||
using TextMateSharp.Grammars;
|
||||
using VisualExtensions = Artemis.UI.Shared.Extensions.VisualExtensions;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Library;
|
||||
|
||||
public partial class SubmissionReleaseView : ReactiveUserControl<SubmissionReleaseViewModel>
|
||||
{
|
||||
private ScrollViewer? _editorScrollViewer;
|
||||
private ScrollViewer? _previewScrollViewer;
|
||||
private bool _updating;
|
||||
|
||||
public SubmissionReleaseView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
DescriptionEditor.Options.AllowScrollBelowDocument = false;
|
||||
RegistryOptions options = new(ThemeName.Dark);
|
||||
TextMate.Installation? install = DescriptionEditor.InstallTextMate(options);
|
||||
|
||||
install.SetGrammar(options.GetScopeByExtension(".md"));
|
||||
|
||||
this.WhenActivated(_ => SetupScrollSync());
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (this.TryFindResource("SystemAccentColorLight3", out object? resource) && resource is Color color)
|
||||
DescriptionEditor.TextArea.TextView.LinkTextForegroundBrush = new ImmutableSolidColorBrush(color);
|
||||
|
||||
base.OnAttachedToVisualTree(e);
|
||||
}
|
||||
|
||||
private void SetupScrollSync()
|
||||
{
|
||||
if (_editorScrollViewer != null)
|
||||
_editorScrollViewer.PropertyChanged -= EditorScrollViewerOnPropertyChanged;
|
||||
if (_previewScrollViewer != null)
|
||||
_previewScrollViewer.PropertyChanged -= PreviewScrollViewerOnPropertyChanged;
|
||||
|
||||
_editorScrollViewer = VisualExtensions.GetVisualChildrenOfType<ScrollViewer>(DescriptionEditor).FirstOrDefault();
|
||||
_previewScrollViewer = VisualExtensions.GetVisualChildrenOfType<ScrollViewer>(DescriptionPreview).FirstOrDefault();
|
||||
|
||||
if (_editorScrollViewer != null)
|
||||
_editorScrollViewer.PropertyChanged += EditorScrollViewerOnPropertyChanged;
|
||||
if (_previewScrollViewer != null)
|
||||
_previewScrollViewer.PropertyChanged += PreviewScrollViewerOnPropertyChanged;
|
||||
}
|
||||
|
||||
private void EditorScrollViewerOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property.Name != nameof(ScrollViewer.Offset) || _updating || SynchronizedScrolling.IsChecked != true)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_updating = true;
|
||||
SynchronizeScrollViewers(_editorScrollViewer, _previewScrollViewer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void PreviewScrollViewerOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property.Name != nameof(ScrollViewer.Offset) || _updating || SynchronizedScrolling.IsChecked != true)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_updating = true;
|
||||
SynchronizeScrollViewers(_previewScrollViewer, _editorScrollViewer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SynchronizeScrollViewers(ScrollViewer? source, ScrollViewer? target)
|
||||
{
|
||||
if (source == null || target == null)
|
||||
return;
|
||||
|
||||
double sourceScrollableHeight = source.Extent.Height - source.Viewport.Height;
|
||||
double targetScrollableHeight = target.Extent.Height - target.Viewport.Height;
|
||||
|
||||
if (sourceScrollableHeight != 0)
|
||||
target.Offset = new Vector(target.Offset.X, targetScrollableHeight * (source.Offset.Y / sourceScrollableHeight));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Screens.Workshop.Parameters;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.Builders;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Avalonia.Layout;
|
||||
using AvaloniaEdit.Document;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
using StrawberryShake;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Library;
|
||||
|
||||
public partial class SubmissionReleaseViewModel : RoutableScreen<ReleaseDetailParameters>
|
||||
{
|
||||
private readonly IWorkshopClient _client;
|
||||
private readonly IRouter _router;
|
||||
private readonly IWindowService _windowService;
|
||||
private readonly INotificationService _notificationService;
|
||||
private readonly ObservableAsPropertyHelper<bool> _hasChanges;
|
||||
|
||||
[Notify] private IGetReleaseById_Release? _release;
|
||||
[Notify] private string _changelog = string.Empty;
|
||||
[Notify] private TextDocument? _markdownDocument;
|
||||
|
||||
public SubmissionReleaseViewModel(IWorkshopClient client, IRouter router, IWindowService windowService, INotificationService notificationService)
|
||||
{
|
||||
_client = client;
|
||||
_router = router;
|
||||
_windowService = windowService;
|
||||
_notificationService = notificationService;
|
||||
_hasChanges = this.WhenAnyValue(vm => vm.Changelog, vm => vm.Release, (current, release) => current != release?.Changelog).ToProperty(this, vm => vm.HasChanges);
|
||||
|
||||
Discard = ReactiveCommand.Create(ExecuteDiscard, this.WhenAnyValue(vm => vm.HasChanges));
|
||||
Save = ReactiveCommand.CreateFromTask(ExecuteSave, this.WhenAnyValue(vm => vm.HasChanges));
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
Disposable.Create(() =>
|
||||
{
|
||||
if (MarkdownDocument != null)
|
||||
MarkdownDocument.TextChanged -= MarkdownDocumentOnTextChanged;
|
||||
}).DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
public bool HasChanges => _hasChanges.Value;
|
||||
public ReactiveCommand<Unit, Unit> Discard { get; set; }
|
||||
public ReactiveCommand<Unit, Unit> Save { get; set; }
|
||||
|
||||
public override async Task OnNavigating(ReleaseDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
|
||||
{
|
||||
IOperationResult<IGetReleaseByIdResult> result = await _client.GetReleaseById.ExecuteAsync(parameters.ReleaseId, cancellationToken);
|
||||
Release = result.Data?.Release;
|
||||
Changelog = Release?.Changelog ?? string.Empty;
|
||||
|
||||
SetupMarkdownDocument();
|
||||
}
|
||||
|
||||
public async Task DeleteRelease()
|
||||
{
|
||||
if (Release == null)
|
||||
return;
|
||||
|
||||
bool confirmed = await _windowService.ShowConfirmContentDialog(
|
||||
"Delete release?",
|
||||
"This cannot be undone.\r\n" +
|
||||
"Users that have already downloaded this release will keep it.");
|
||||
if (!confirmed)
|
||||
return;
|
||||
|
||||
await _client.RemoveRelease.ExecuteAsync(Release.Id);
|
||||
_notificationService.CreateNotification()
|
||||
.WithTitle("Deleted release.")
|
||||
.WithSeverity(NotificationSeverity.Success)
|
||||
.WithHorizontalPosition(HorizontalAlignment.Left)
|
||||
.Show();
|
||||
|
||||
await Close();
|
||||
}
|
||||
|
||||
public async Task Close()
|
||||
{
|
||||
await _router.GoUp();
|
||||
}
|
||||
|
||||
private async Task ExecuteSave(CancellationToken cancellationToken)
|
||||
{
|
||||
if (Release == null)
|
||||
return;
|
||||
|
||||
await _client.UpdateRelease.ExecuteAsync(new UpdateReleaseInput {Id = Release.Id, Changelog = Changelog}, cancellationToken);
|
||||
_notificationService.CreateNotification()
|
||||
.WithTitle("Saved changelog.")
|
||||
.WithSeverity(NotificationSeverity.Success)
|
||||
.WithHorizontalPosition(HorizontalAlignment.Left)
|
||||
.Show();
|
||||
}
|
||||
|
||||
private void ExecuteDiscard()
|
||||
{
|
||||
Changelog = Release?.Changelog ?? string.Empty;
|
||||
SetupMarkdownDocument();
|
||||
}
|
||||
|
||||
private void SetupMarkdownDocument()
|
||||
{
|
||||
if (MarkdownDocument != null)
|
||||
MarkdownDocument.TextChanged -= MarkdownDocumentOnTextChanged;
|
||||
|
||||
MarkdownDocument = new TextDocument(new StringTextSource(Changelog));
|
||||
MarkdownDocument.TextChanged += MarkdownDocumentOnTextChanged;
|
||||
}
|
||||
|
||||
private void MarkdownDocumentOnTextChanged(object? sender, EventArgs e)
|
||||
{
|
||||
Changelog = MarkdownDocument?.Text ?? string.Empty;
|
||||
}
|
||||
}
|
||||
@ -3,3 +3,16 @@ mutation UpdateEntry ($input: UpdateEntryInput!) {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
mutation UpdateRelease($input: UpdateReleaseInput!) {
|
||||
updateRelease(input: $input) {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
mutation RemoveRelease($input: Long!) {
|
||||
removeRelease(id: $input) {
|
||||
id
|
||||
}
|
||||
}
|
||||
@ -14,5 +14,10 @@ query GetSubmittedEntryById($id: Long!) {
|
||||
images {
|
||||
...image
|
||||
}
|
||||
releases {
|
||||
id
|
||||
version
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,10 +2,10 @@ namespace Artemis.WebClient.Workshop;
|
||||
|
||||
public static class WorkshopConstants
|
||||
{
|
||||
// public const string AUTHORITY_URL = "https://localhost:5001";
|
||||
// public const string WORKSHOP_URL = "https://localhost:7281";
|
||||
public const string AUTHORITY_URL = "https://identity.artemis-rgb.com";
|
||||
public const string WORKSHOP_URL = "https://workshop.artemis-rgb.com";
|
||||
public const string AUTHORITY_URL = "https://localhost:5001";
|
||||
public const string WORKSHOP_URL = "https://localhost:7281";
|
||||
// public const string AUTHORITY_URL = "https://identity.artemis-rgb.com";
|
||||
// public const string WORKSHOP_URL = "https://workshop.artemis-rgb.com";
|
||||
public const string IDENTITY_CLIENT_NAME = "IdentityApiClient";
|
||||
public const string WORKSHOP_CLIENT_NAME = "WorkshopApiClient";
|
||||
}
|
||||
@ -2,7 +2,7 @@ schema: schema.graphql
|
||||
extensions:
|
||||
endpoints:
|
||||
Default GraphQL Endpoint:
|
||||
url: https://workshop.artemis-rgb.com/graphql
|
||||
url: https://localhost:7281/graphql
|
||||
headers:
|
||||
user-agent: JS GraphQL
|
||||
introspect: true
|
||||
|
||||
@ -100,6 +100,7 @@ type Mutation {
|
||||
addLayoutInfo(input: CreateLayoutInfoInput!): LayoutInfo
|
||||
removeEntry(id: Long!): Entry
|
||||
removeLayoutInfo(id: Long!): LayoutInfo!
|
||||
removeRelease(id: Long!): Release!
|
||||
updateEntry(input: UpdateEntryInput!): Entry
|
||||
updateEntryImage(input: UpdateEntryImageInput!): Image
|
||||
updateRelease(input: UpdateReleaseInput!): Release
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user