From 7b71ee05da705f0ec8af2307bdbb9a60803e18b5 Mon Sep 17 00:00:00 2001 From: RobertBeekman Date: Sat, 13 Apr 2024 16:54:57 +0200 Subject: [PATCH] Workshop - Refactor markdown editor Workshop - Add changelog during upload --- .../Controls/SplitMarkdownEditor.axaml | 51 ++++++ .../Controls/SplitMarkdownEditor.axaml.cs | 150 ++++++++++++++++++ .../Details/EntrySpecificationsView.axaml | 50 +----- .../Details/EntrySpecificationsView.axaml.cs | 89 ----------- .../Details/EntrySpecificationsViewModel.cs | 18 +-- .../Library/SubmissionManagementViewModel.cs | 2 + .../Library/SubmissionReleaseView.axaml | 50 +----- .../Library/SubmissionReleaseView.axaml.cs | 89 ----------- .../Library/SubmissionReleaseViewModel.cs | 50 +++--- .../Library/WorkshopLibraryViewModel.cs | 2 +- .../Profile/ProfileDescriptionView.axaml | 2 +- .../Models/SubmissionWizardState.cs | 1 + .../Steps/ChangelogStepView.axaml | 27 ++++ .../Steps/ChangelogStepView.axaml.cs | 14 ++ .../Steps/ChangelogStepViewModel.cs | 39 +++++ .../Steps/Layout/LayoutInfoStepViewModel.cs | 2 +- .../Plugin/PluginSelectionStepViewModel.cs | 2 +- .../ProfileAdaptionHintsStepViewModel.cs | 2 +- .../Steps/UploadStepViewModel.cs | 2 +- .../UploadHandlers/IEntryUploadHandler.cs | 6 +- .../LayoutEntryUploadHandler.cs | 4 +- .../PluginEntryUploadHandler.cs | 4 +- .../ProfileEntryUploadHandler.cs | 4 +- .../WorkshopConstants.cs | 8 +- 24 files changed, 334 insertions(+), 334 deletions(-) create mode 100644 src/Artemis.UI/Controls/SplitMarkdownEditor.axaml create mode 100644 src/Artemis.UI/Controls/SplitMarkdownEditor.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepView.axaml create mode 100644 src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepView.axaml.cs create mode 100644 src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepViewModel.cs diff --git a/src/Artemis.UI/Controls/SplitMarkdownEditor.axaml b/src/Artemis.UI/Controls/SplitMarkdownEditor.axaml new file mode 100644 index 000000000..f99eba062 --- /dev/null +++ b/src/Artemis.UI/Controls/SplitMarkdownEditor.axaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Controls/SplitMarkdownEditor.axaml.cs b/src/Artemis.UI/Controls/SplitMarkdownEditor.axaml.cs new file mode 100644 index 000000000..ec50e2e0a --- /dev/null +++ b/src/Artemis.UI/Controls/SplitMarkdownEditor.axaml.cs @@ -0,0 +1,150 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Media; +using Avalonia.Media.Immutable; +using Avalonia.Threading; +using AvaloniaEdit.TextMate; +using TextMateSharp.Grammars; +using VisualExtensions = Artemis.UI.Shared.Extensions.VisualExtensions; + +namespace Artemis.UI.Controls; + +public partial class SplitMarkdownEditor : UserControl +{ + public static readonly StyledProperty TitleProperty = AvaloniaProperty.Register(nameof(Title), string.Empty); + public static readonly StyledProperty MarkdownProperty = AvaloniaProperty.Register(nameof(Markdown), string.Empty, defaultBindingMode: BindingMode.TwoWay); + + private ScrollViewer? _editorScrollViewer; + private ScrollViewer? _previewScrollViewer; + private bool _scrolling; + private bool _updating; + + public string Title + { + get => GetValue(TitleProperty); + set => SetValue(TitleProperty, value); + } + + public string Markdown + { + get => GetValue(MarkdownProperty); + set => SetValue(MarkdownProperty, value); + } + + public SplitMarkdownEditor() + { + InitializeComponent(); + PropertyChanged += OnPropertyChanged; + + DescriptionEditorLabel.Content = Title; + DescriptionEditor.Options.AllowScrollBelowDocument = false; + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + if (this.TryFindResource("SystemAccentColorLight3", out object? resource) && resource is Color color) + DescriptionEditor.TextArea.TextView.LinkTextForegroundBrush = new ImmutableSolidColorBrush(color); + + SetupScrollSync(); + + Dispatcher.UIThread.InvokeAsync(async () => + { + // Installing is slow, wait for UI to settle + await Task.Delay(300); + + RegistryOptions options = new(ThemeName.Dark); + TextMate.Installation? install = DescriptionEditor.InstallTextMate(options); + install.SetGrammar(options.GetScopeByExtension(".md")); + }, DispatcherPriority.ApplicationIdle); + } + + private void SetupScrollSync() + { + if (_editorScrollViewer != null) + _editorScrollViewer.PropertyChanged -= EditorScrollViewerOnPropertyChanged; + if (_previewScrollViewer != null) + _previewScrollViewer.PropertyChanged -= PreviewScrollViewerOnPropertyChanged; + + _editorScrollViewer = VisualExtensions.GetVisualChildrenOfType(DescriptionEditor).FirstOrDefault(); + _previewScrollViewer = VisualExtensions.GetVisualChildrenOfType(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) || _scrolling || SynchronizedScrolling.IsChecked != true) + return; + + try + { + _scrolling = true; + SynchronizeScrollViewers(_editorScrollViewer, _previewScrollViewer); + } + finally + { + _scrolling = false; + } + } + + private void PreviewScrollViewerOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property.Name != nameof(ScrollViewer.Offset) || _scrolling || SynchronizedScrolling.IsChecked != true) + return; + + try + { + _scrolling = true; + SynchronizeScrollViewers(_previewScrollViewer, _editorScrollViewer); + } + finally + { + _scrolling = 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)); + } + + private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property == TitleProperty) + DescriptionEditorLabel.Content = Title; + else if (e.Property == MarkdownProperty && DescriptionEditor.Text != Markdown) + { + try + { + _updating = true; + DescriptionEditor.Clear(); + DescriptionEditor.AppendText(Markdown); + } + finally + { + _updating = false; + } + + } + } + + private void DescriptionEditor_OnTextChanged(object? sender, EventArgs e) + { + if (!_updating && Markdown != DescriptionEditor.Text) + Markdown = DescriptionEditor.Text; + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsView.axaml index 8b7fb56e6..16a0b4b5a 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsView.axaml @@ -2,18 +2,15 @@ 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:entries="clr-namespace:Artemis.UI.Screens.Workshop.Entries" xmlns:tagsInput="clr-namespace:Artemis.UI.Shared.TagsInput;assembly=Artemis.UI.Shared" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:categories="clr-namespace:Artemis.UI.Screens.Workshop.Categories" - 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" xmlns:details="clr-namespace:Artemis.UI.Screens.Workshop.Entries.Details" + xmlns:controls="clr-namespace:Artemis.UI.Controls" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800" x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntrySpecificationsView" x:DataType="details:EntrySpecificationsViewModel"> - + @@ -95,48 +92,9 @@ - - - - - Synchronized scrolling - - - - - - - - - - - - - - - - - - + - diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsView.axaml.cs index 37c85d118..1374349e9 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsView.axaml.cs @@ -1,100 +1,11 @@ -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.Entries.Details; public partial class EntrySpecificationsView : ReactiveUserControl { - private ScrollViewer? _editorScrollViewer; - private ScrollViewer? _previewScrollViewer; - private bool _updating; - public EntrySpecificationsView() { InitializeComponent(); - - DescriptionEditor.Options.AllowScrollBelowDocument = false; - RegistryOptions options = new(ThemeName.Dark); - TextMate.Installation? install = TextMate.InstallTextMate(DescriptionEditor, 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(DescriptionEditor).FirstOrDefault(); - _previewScrollViewer = VisualExtensions.GetVisualChildrenOfType(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)); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsViewModel.cs index f06262a3f..d277f9f62 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/Details/EntrySpecificationsViewModel.cs @@ -35,7 +35,6 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase [Notify] private string _summary = string.Empty; [Notify] private string _description = string.Empty; [Notify] private Bitmap? _iconBitmap; - [Notify] private TextDocument? _markdownDocument; [Notify(Setter.Private)] private bool _iconChanged; public EntrySpecificationsViewModel(IWorkshopClient workshopClient, IWindowService windowService) @@ -69,15 +68,7 @@ public EntrySpecificationsViewModel(IWorkshopClient workshopClient, IWindowServi { // Load categories await PopulateCategories(); - - MarkdownDocument = new TextDocument(new StringTextSource(Description)); - MarkdownDocument.TextChanged += MarkdownDocumentOnTextChanged; - Disposable.Create(() => - { - MarkdownDocument.TextChanged -= MarkdownDocumentOnTextChanged; - MarkdownDocument = null; - ClearIcon(); - }).DisposeWith(d); + Disposable.Create(ClearIcon).DisposeWith(d); }); } @@ -92,12 +83,7 @@ public EntrySpecificationsViewModel(IWorkshopClient workshopClient, IWindowServi public bool DescriptionValid => _descriptionValid.Value; public List PreselectedCategories { get; set; } = new(); - - private void MarkdownDocumentOnTextChanged(object? sender, EventArgs e) - { - Description = MarkdownDocument?.Text ?? string.Empty; - } - + private async Task ExecuteSelectIcon() { string[]? result = await _windowService.CreateOpenFileDialog() diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionManagementViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionManagementViewModel.cs index 3b1feb76a..7146f6307 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/SubmissionManagementViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionManagementViewModel.cs @@ -36,6 +36,8 @@ public SubmissionManagementViewModel(IWorkshopClient client, IRouter router, IWi _windowService = windowService; _workshopService = workshopService; + RecycleScreen = false; + this.WhenActivated(d => { this.WhenAnyValue(vm => vm.SelectedRelease) diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml b/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml index 105417bf4..691e767a1 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml @@ -4,13 +4,11 @@ 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" + xmlns:controls1="clr-namespace:Artemis.UI.Controls" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Workshop.Library.SubmissionReleaseView" x:DataType="library:SubmissionReleaseViewModel"> - + diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml.cs index 503c3cb87..de58a90f6 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseView.axaml.cs @@ -1,100 +1,11 @@ -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 { - 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(DescriptionEditor).FirstOrDefault(); - _previewScrollViewer = VisualExtensions.GetVisualChildrenOfType(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)); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseViewModel.cs index eb02f12b6..025d2ce5a 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionReleaseViewModel.cs @@ -22,11 +22,10 @@ public partial class SubmissionReleaseViewModel : RoutableScreen _hasChanges; [Notify] private IGetReleaseById_Release? _release; - [Notify] private string _changelog = string.Empty; - [Notify] private TextDocument? _markdownDocument; + [Notify] private string? _changelog; + [Notify] private bool _hasChanges; public SubmissionReleaseViewModel(IWorkshopClient client, IRouter router, IWindowService windowService, INotificationService notificationService) { @@ -34,22 +33,12 @@ public SubmissionReleaseViewModel(IWorkshopClient client, IRouter router, IWindo _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); + this.WhenAnyValue(vm => vm.Changelog, vm => vm.Release, (current, release) => current != release?.Changelog).Subscribe(hasChanges => HasChanges = 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 Discard { get; set; } public ReactiveCommand Save { get; set; } @@ -57,9 +46,17 @@ public override async Task OnNavigating(ReleaseDetailParameters parameters, Navi { IOperationResult result = await _client.GetReleaseById.ExecuteAsync(parameters.ReleaseId, cancellationToken); Release = result.Data?.Release; - Changelog = Release?.Changelog ?? string.Empty; + Changelog = Release?.Changelog; + } + + public override async Task OnClosing(NavigationArguments args) + { + if (!HasChanges) + return; - SetupMarkdownDocument(); + bool confirmed = await _windowService.ShowConfirmContentDialog("You have unsaved changes", "Do you want to discard your unsaved changes?"); + if (!confirmed) + args.Cancel(); } public async Task DeleteRelease() @@ -81,6 +78,7 @@ public async Task DeleteRelease() .WithHorizontalPosition(HorizontalAlignment.Left) .Show(); + HasChanges = false; await Close(); } @@ -100,25 +98,13 @@ private async Task ExecuteSave(CancellationToken cancellationToken) .WithSeverity(NotificationSeverity.Success) .WithHorizontalPosition(HorizontalAlignment.Left) .Show(); - } - private void ExecuteDiscard() - { - Changelog = Release?.Changelog ?? string.Empty; - SetupMarkdownDocument(); + HasChanges = false; } - 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) + private void ExecuteDiscard() { - Changelog = MarkdownDocument?.Text ?? string.Empty; + Changelog = Release?.Changelog; + HasChanges = false; } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/WorkshopLibraryViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/WorkshopLibraryViewModel.cs index 413914803..af02fa985 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/WorkshopLibraryViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/WorkshopLibraryViewModel.cs @@ -53,7 +53,7 @@ public override async Task OnNavigating(NavigationArguments args, CancellationTo public void GoBack() { if (ViewingDetails) - _router.GoBack(); + _router.Navigate("workshop/library/submissions"); else _router.Navigate("workshop"); } diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionView.axaml b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionView.axaml index ff076cdb6..5b29dc430 100644 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDescriptionView.axaml @@ -9,7 +9,7 @@ x:DataType="profile:ProfileDescriptionViewModel"> - + diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Models/SubmissionWizardState.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Models/SubmissionWizardState.cs index c92f6290b..aaf688ef6 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Models/SubmissionWizardState.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Models/SubmissionWizardState.cs @@ -38,6 +38,7 @@ public SubmissionWizardState(IWorkshopWizardViewModel wizardViewModel, IContaine public List Images { get; set; } = new(); public IEntrySource? EntrySource { get; set; } + public string? Changelog { get; set; } public void ChangeScreen() where TSubmissionViewModel : SubmissionViewModel { diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepView.axaml b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepView.axaml new file mode 100644 index 000000000..ff6ddd67d --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepView.axaml @@ -0,0 +1,27 @@ + + + + + + + + + Changelog + + If you want to inform your users what has changed in this release, you can provide a changelog. This is optional but recommended. + + + + + + diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepView.axaml.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepView.axaml.cs new file mode 100644 index 000000000..2733746f7 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps; + +public partial class ChangelogStepView : ReactiveUserControl +{ + public ChangelogStepView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepViewModel.cs new file mode 100644 index 000000000..0333e5a56 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ChangelogStepViewModel.cs @@ -0,0 +1,39 @@ +using System.Reactive.Disposables; +using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Layout; +using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile; +using Artemis.WebClient.Workshop; +using PropertyChanged.SourceGenerator; +using ReactiveUI; + +namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps; + +public partial class ChangelogStepViewModel : SubmissionViewModel +{ + [Notify] private string? _changelog; + + public ChangelogStepViewModel() + { + GoBack = ReactiveCommand.Create(ExecuteGoBack); + Continue = ReactiveCommand.Create(ExecuteContinue); + ContinueText = "Submit"; + + this.WhenActivated((CompositeDisposable _) => Changelog = State.Changelog); + } + + private void ExecuteContinue() + { + State.Changelog = Changelog; + State.ChangeScreen(); + } + + private void ExecuteGoBack() + { + State.Changelog = Changelog; + if (State.EntryType == EntryType.Layout) + State.ChangeScreen(); + else if (State.EntryType == EntryType.Plugin) + State.ChangeScreen(); + else if (State.EntryType == EntryType.Profile) + State.ChangeScreen(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutInfoStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutInfoStepViewModel.cs index 23c3b293e..3ebe3b748 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutInfoStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutInfoStepViewModel.cs @@ -98,6 +98,6 @@ private void ExecuteContinue() if (State.EntryId == null) State.ChangeScreen(); else - State.ChangeScreen(); + State.ChangeScreen(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Plugin/PluginSelectionStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Plugin/PluginSelectionStepViewModel.cs index 2d8cc45d9..4008c5f57 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Plugin/PluginSelectionStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Plugin/PluginSelectionStepViewModel.cs @@ -78,6 +78,6 @@ private void ExecuteContinue() if (State.EntryId == null) State.ChangeScreen(); else - State.ChangeScreen(); + State.ChangeScreen(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileAdaptionHintsStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileAdaptionHintsStepViewModel.cs index 0e3556a96..87d7872dc 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileAdaptionHintsStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileAdaptionHintsStepViewModel.cs @@ -64,6 +64,6 @@ private void ExecuteContinue() if (State.EntryId == null) State.ChangeScreen(); else - State.ChangeScreen(); + State.ChangeScreen(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs index 81e464311..c205e24aa 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs @@ -70,7 +70,7 @@ private async Task ExecuteUpload(CancellationToken cancellationToken) // Create a release for the new entry IEntryUploadHandler uploadHandler = _entryUploadHandlerFactory.CreateHandler(State.EntryType); - EntryUploadResult uploadResult = await uploadHandler.CreateReleaseAsync(_entryId.Value, State.EntrySource!, cancellationToken); + EntryUploadResult uploadResult = await uploadHandler.CreateReleaseAsync(_entryId.Value, State.EntrySource!, State.Changelog, cancellationToken); if (!uploadResult.IsSuccess) throw new ArtemisWorkshopException(uploadResult.Message); diff --git a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/IEntryUploadHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/IEntryUploadHandler.cs index c0bc829be..a25dc689b 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/IEntryUploadHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/IEntryUploadHandler.cs @@ -1,8 +1,6 @@ -using Artemis.UI.Shared.Utilities; - -namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers; +namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers; public interface IEntryUploadHandler { - Task CreateReleaseAsync(long entryId, IEntrySource entrySource, CancellationToken cancellationToken); + Task CreateReleaseAsync(long entryId, IEntrySource entrySource, string? changelog, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs index 38e2c9789..0ed8edfc6 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs @@ -18,7 +18,7 @@ public LayoutEntryUploadHandler(IHttpClientFactory httpClientFactory) } /// - public async Task CreateReleaseAsync(long entryId, IEntrySource entrySource, CancellationToken cancellationToken) + public async Task CreateReleaseAsync(long entryId, IEntrySource entrySource, string? changelog, CancellationToken cancellationToken) { if (entrySource is not LayoutEntrySource source) throw new InvalidOperationException("Can only create releases for layouts"); @@ -62,6 +62,8 @@ public async Task CreateReleaseAsync(long entryId, IEntrySour MultipartFormDataContent content = new(); StreamContent streamContent = new(archiveStream); streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); + if (!string.IsNullOrWhiteSpace(changelog)) + content.Add(new StringContent(changelog), "Changelog"); content.Add(streamContent, "file", "file.zip"); // Submit diff --git a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/PluginEntryUploadHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/PluginEntryUploadHandler.cs index f8c51034c..926f8485b 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/PluginEntryUploadHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/PluginEntryUploadHandler.cs @@ -14,7 +14,7 @@ public PluginEntryUploadHandler(IHttpClientFactory httpClientFactory) } /// - public async Task CreateReleaseAsync(long entryId, IEntrySource entrySource, CancellationToken cancellationToken) + public async Task CreateReleaseAsync(long entryId, IEntrySource entrySource, string? changelog, CancellationToken cancellationToken) { if (entrySource is not PluginEntrySource source) throw new InvalidOperationException("Can only create releases for plugins"); @@ -27,6 +27,8 @@ public async Task CreateReleaseAsync(long entryId, IEntrySour MultipartFormDataContent content = new(); StreamContent streamContent = new(fileStream); streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); + if (!string.IsNullOrWhiteSpace(changelog)) + content.Add(new StringContent(changelog), "Changelog"); content.Add(streamContent, "file", "file.zip"); // Submit diff --git a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs index 3dbbd68e9..381bd0199 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/ProfileEntryUploadHandler.cs @@ -17,7 +17,7 @@ public ProfileEntryUploadHandler(IHttpClientFactory httpClientFactory, IProfileS } /// - public async Task CreateReleaseAsync(long entryId, IEntrySource entrySource, CancellationToken cancellationToken) + public async Task CreateReleaseAsync(long entryId, IEntrySource entrySource, string? changelog, CancellationToken cancellationToken) { if (entrySource is not ProfileEntrySource source) throw new InvalidOperationException("Can only create releases for profile configurations"); @@ -32,6 +32,8 @@ public async Task CreateReleaseAsync(long entryId, IEntrySour StreamContent streamContent = new(archiveStream); streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); content.Add(JsonContent.Create(source.Dependencies.Select(d => new {PluginId = d.Plugin.Guid, FeatureId = d.Id}).ToList()), "ReleaseDependencies"); + if (!string.IsNullOrWhiteSpace(changelog)) + content.Add(new StringContent(changelog), "Changelog"); content.Add(streamContent, "file", "file.zip"); // Submit diff --git a/src/Artemis.WebClient.Workshop/WorkshopConstants.cs b/src/Artemis.WebClient.Workshop/WorkshopConstants.cs index 907ddb843..10807064c 100644 --- a/src/Artemis.WebClient.Workshop/WorkshopConstants.cs +++ b/src/Artemis.WebClient.Workshop/WorkshopConstants.cs @@ -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"; } \ No newline at end of file