mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-12 21:38:38 +00:00
Workshop - Refactor markdown editor
Workshop - Add changelog during upload
This commit is contained in:
parent
62057d657a
commit
7b71ee05da
51
src/Artemis.UI/Controls/SplitMarkdownEditor.axaml
Normal file
51
src/Artemis.UI/Controls/SplitMarkdownEditor.axaml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<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:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
|
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
|
||||||
|
xmlns:fa="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Controls.SplitMarkdownEditor">
|
||||||
|
<Grid RowDefinitions="Auto,*">
|
||||||
|
<Grid Row="0" ColumnDefinitions="Auto,*">
|
||||||
|
<Label Grid.Column="0" Name="DescriptionEditorLabel" Target="DescriptionEditor" Margin="0 28 0 0" />
|
||||||
|
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||||
|
<CheckBox Name="SynchronizedScrolling" IsChecked="True" VerticalAlignment="Bottom">Synchronized scrolling</CheckBox>
|
||||||
|
<fa: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="1" 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"
|
||||||
|
TextChanged="DescriptionEditor_OnTextChanged"
|
||||||
|
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 Document.Text, Mode=OneWay, ElementName=DescriptionEditor}"
|
||||||
|
MarkdownStyleName="FluentAvalonia"
|
||||||
|
SaveScrollValueWhenContentUpdated="True">
|
||||||
|
<mdxaml:MarkdownScrollViewer.Styles>
|
||||||
|
<StyleInclude Source="/Styles/Markdown.axaml" />
|
||||||
|
</mdxaml:MarkdownScrollViewer.Styles>
|
||||||
|
</mdxaml:MarkdownScrollViewer>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
150
src/Artemis.UI/Controls/SplitMarkdownEditor.axaml.cs
Normal file
150
src/Artemis.UI/Controls/SplitMarkdownEditor.axaml.cs
Normal file
@ -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<string> TitleProperty = AvaloniaProperty.Register<SplitMarkdownEditor, string>(nameof(Title), string.Empty);
|
||||||
|
public static readonly StyledProperty<string> MarkdownProperty = AvaloniaProperty.Register<SplitMarkdownEditor, string>(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<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) || _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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,18 +2,15 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:entries="clr-namespace:Artemis.UI.Screens.Workshop.Entries"
|
|
||||||
xmlns:tagsInput="clr-namespace:Artemis.UI.Shared.TagsInput;assembly=Artemis.UI.Shared"
|
xmlns:tagsInput="clr-namespace:Artemis.UI.Shared.TagsInput;assembly=Artemis.UI.Shared"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:categories="clr-namespace:Artemis.UI.Screens.Workshop.Categories"
|
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: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"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800"
|
||||||
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntrySpecificationsView"
|
x:Class="Artemis.UI.Screens.Workshop.Entries.Details.EntrySpecificationsView"
|
||||||
x:DataType="details:EntrySpecificationsViewModel">
|
x:DataType="details:EntrySpecificationsViewModel">
|
||||||
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
<Grid RowDefinitions="Auto,*,Auto">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<StackPanel.Styles>
|
<StackPanel.Styles>
|
||||||
<Styles>
|
<Styles>
|
||||||
@ -95,48 +92,9 @@
|
|||||||
<tagsInput:TagsInput Tags="{CompiledBinding Tags}" />
|
<tagsInput:TagsInput Tags="{CompiledBinding Tags}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Grid Row="1" ColumnDefinitions="Auto,*">
|
<controls:SplitMarkdownEditor Grid.Row="1" Title="Description" Markdown="{CompiledBinding Description}"/>
|
||||||
<Label Grid.Column="0" Target="DescriptionEditor" Margin="0 28 0 0">Description</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" 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 Description}"
|
|
||||||
MarkdownStyleName="FluentAvalonia"
|
|
||||||
SaveScrollValueWhenContentUpdated="True">
|
|
||||||
<mdxaml:MarkdownScrollViewer.Styles>
|
|
||||||
<StyleInclude Source="/Styles/Markdown.axaml" />
|
|
||||||
</mdxaml:MarkdownScrollViewer.Styles>
|
|
||||||
</mdxaml:MarkdownScrollViewer>
|
|
||||||
</Border>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="3"
|
<TextBlock Grid.Row="2"
|
||||||
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
|
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
|
||||||
Margin="2 8 0 0"
|
Margin="2 8 0 0"
|
||||||
IsVisible="{CompiledBinding !DescriptionValid}">
|
IsVisible="{CompiledBinding !DescriptionValid}">
|
||||||
|
|||||||
@ -1,100 +1,11 @@
|
|||||||
using System.Linq;
|
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Media;
|
|
||||||
using Avalonia.Media.Immutable;
|
|
||||||
using Avalonia.ReactiveUI;
|
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;
|
namespace Artemis.UI.Screens.Workshop.Entries.Details;
|
||||||
|
|
||||||
public partial class EntrySpecificationsView : ReactiveUserControl<EntrySpecificationsViewModel>
|
public partial class EntrySpecificationsView : ReactiveUserControl<EntrySpecificationsViewModel>
|
||||||
{
|
{
|
||||||
private ScrollViewer? _editorScrollViewer;
|
|
||||||
private ScrollViewer? _previewScrollViewer;
|
|
||||||
private bool _updating;
|
|
||||||
|
|
||||||
public EntrySpecificationsView()
|
public EntrySpecificationsView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
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<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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -35,7 +35,6 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
|
|||||||
[Notify] private string _summary = string.Empty;
|
[Notify] private string _summary = string.Empty;
|
||||||
[Notify] private string _description = string.Empty;
|
[Notify] private string _description = string.Empty;
|
||||||
[Notify] private Bitmap? _iconBitmap;
|
[Notify] private Bitmap? _iconBitmap;
|
||||||
[Notify] private TextDocument? _markdownDocument;
|
|
||||||
[Notify(Setter.Private)] private bool _iconChanged;
|
[Notify(Setter.Private)] private bool _iconChanged;
|
||||||
|
|
||||||
public EntrySpecificationsViewModel(IWorkshopClient workshopClient, IWindowService windowService)
|
public EntrySpecificationsViewModel(IWorkshopClient workshopClient, IWindowService windowService)
|
||||||
@ -69,15 +68,7 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
|
|||||||
{
|
{
|
||||||
// Load categories
|
// Load categories
|
||||||
await PopulateCategories();
|
await PopulateCategories();
|
||||||
|
Disposable.Create(ClearIcon).DisposeWith(d);
|
||||||
MarkdownDocument = new TextDocument(new StringTextSource(Description));
|
|
||||||
MarkdownDocument.TextChanged += MarkdownDocumentOnTextChanged;
|
|
||||||
Disposable.Create(() =>
|
|
||||||
{
|
|
||||||
MarkdownDocument.TextChanged -= MarkdownDocumentOnTextChanged;
|
|
||||||
MarkdownDocument = null;
|
|
||||||
ClearIcon();
|
|
||||||
}).DisposeWith(d);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,12 +83,7 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
|
|||||||
public bool DescriptionValid => _descriptionValid.Value;
|
public bool DescriptionValid => _descriptionValid.Value;
|
||||||
|
|
||||||
public List<long> PreselectedCategories { get; set; } = new();
|
public List<long> PreselectedCategories { get; set; } = new();
|
||||||
|
|
||||||
private void MarkdownDocumentOnTextChanged(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
Description = MarkdownDocument?.Text ?? string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ExecuteSelectIcon()
|
private async Task ExecuteSelectIcon()
|
||||||
{
|
{
|
||||||
string[]? result = await _windowService.CreateOpenFileDialog()
|
string[]? result = await _windowService.CreateOpenFileDialog()
|
||||||
|
|||||||
@ -36,6 +36,8 @@ public partial class SubmissionManagementViewModel : RoutableHostScreen<Routable
|
|||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
_workshopService = workshopService;
|
_workshopService = workshopService;
|
||||||
|
|
||||||
|
RecycleScreen = false;
|
||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
this.WhenAnyValue(vm => vm.SelectedRelease)
|
this.WhenAnyValue(vm => vm.SelectedRelease)
|
||||||
|
|||||||
@ -4,13 +4,11 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:library="clr-namespace:Artemis.UI.Screens.Workshop.Library"
|
xmlns:library="clr-namespace:Artemis.UI.Screens.Workshop.Library"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
xmlns:controls1="clr-namespace:Artemis.UI.Controls"
|
||||||
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"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.Workshop.Library.SubmissionReleaseView"
|
x:Class="Artemis.UI.Screens.Workshop.Library.SubmissionReleaseView"
|
||||||
x:DataType="library:SubmissionReleaseViewModel">
|
x:DataType="library:SubmissionReleaseViewModel">
|
||||||
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
<Grid RowDefinitions="Auto,*,Auto">
|
||||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="5">
|
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="5">
|
||||||
<Button VerticalAlignment="Center" Classes="icon-button" Command="{CompiledBinding Close}">
|
<Button VerticalAlignment="Center" Classes="icon-button" Command="{CompiledBinding Close}">
|
||||||
<avalonia:MaterialIcon Kind="ArrowBack" />
|
<avalonia:MaterialIcon Kind="ArrowBack" />
|
||||||
@ -18,47 +16,9 @@
|
|||||||
<TextBlock Classes="h3 no-margin" Text="{CompiledBinding Release.Version}" />
|
<TextBlock Classes="h3 no-margin" Text="{CompiledBinding Release.Version}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Grid Row="1" ColumnDefinitions="Auto,*">
|
<controls1:SplitMarkdownEditor Grid.Row="1" Title="Changelog" Markdown="{CompiledBinding Changelog}"/>
|
||||||
<Label Grid.Column="0" Target="DescriptionEditor" Margin="0 28 0 0">Changelog</Label>
|
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
|
<StackPanel Grid.Row="2" Margin="0 10 0 0" Orientation="Horizontal" Spacing="5" 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 Classes="danger" Command="{CompiledBinding DeleteRelease}">Delete release</Button>
|
||||||
<Button Command="{CompiledBinding Discard}">Discard changes</Button>
|
<Button Command="{CompiledBinding Discard}">Discard changes</Button>
|
||||||
<Button Command="{CompiledBinding Save}">Save</Button>
|
<Button Command="{CompiledBinding Save}">Save</Button>
|
||||||
|
|||||||
@ -1,100 +1,11 @@
|
|||||||
using System.Linq;
|
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Media;
|
|
||||||
using Avalonia.Media.Immutable;
|
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using AvaloniaEdit.TextMate;
|
|
||||||
using ReactiveUI;
|
|
||||||
using TextMateSharp.Grammars;
|
|
||||||
using VisualExtensions = Artemis.UI.Shared.Extensions.VisualExtensions;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Library;
|
namespace Artemis.UI.Screens.Workshop.Library;
|
||||||
|
|
||||||
public partial class SubmissionReleaseView : ReactiveUserControl<SubmissionReleaseViewModel>
|
public partial class SubmissionReleaseView : ReactiveUserControl<SubmissionReleaseViewModel>
|
||||||
{
|
{
|
||||||
private ScrollViewer? _editorScrollViewer;
|
|
||||||
private ScrollViewer? _previewScrollViewer;
|
|
||||||
private bool _updating;
|
|
||||||
|
|
||||||
public SubmissionReleaseView()
|
public SubmissionReleaseView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -22,11 +22,10 @@ public partial class SubmissionReleaseViewModel : RoutableScreen<ReleaseDetailPa
|
|||||||
private readonly IRouter _router;
|
private readonly IRouter _router;
|
||||||
private readonly IWindowService _windowService;
|
private readonly IWindowService _windowService;
|
||||||
private readonly INotificationService _notificationService;
|
private readonly INotificationService _notificationService;
|
||||||
private readonly ObservableAsPropertyHelper<bool> _hasChanges;
|
|
||||||
|
|
||||||
[Notify] private IGetReleaseById_Release? _release;
|
[Notify] private IGetReleaseById_Release? _release;
|
||||||
[Notify] private string _changelog = string.Empty;
|
[Notify] private string? _changelog;
|
||||||
[Notify] private TextDocument? _markdownDocument;
|
[Notify] private bool _hasChanges;
|
||||||
|
|
||||||
public SubmissionReleaseViewModel(IWorkshopClient client, IRouter router, IWindowService windowService, INotificationService notificationService)
|
public SubmissionReleaseViewModel(IWorkshopClient client, IRouter router, IWindowService windowService, INotificationService notificationService)
|
||||||
{
|
{
|
||||||
@ -34,22 +33,12 @@ public partial class SubmissionReleaseViewModel : RoutableScreen<ReleaseDetailPa
|
|||||||
_router = router;
|
_router = router;
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
_notificationService = notificationService;
|
_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));
|
Discard = ReactiveCommand.Create(ExecuteDiscard, this.WhenAnyValue(vm => vm.HasChanges));
|
||||||
Save = ReactiveCommand.CreateFromTask(ExecuteSave, 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> Discard { get; set; }
|
||||||
public ReactiveCommand<Unit, Unit> Save { get; set; }
|
public ReactiveCommand<Unit, Unit> Save { get; set; }
|
||||||
|
|
||||||
@ -57,9 +46,17 @@ public partial class SubmissionReleaseViewModel : RoutableScreen<ReleaseDetailPa
|
|||||||
{
|
{
|
||||||
IOperationResult<IGetReleaseByIdResult> result = await _client.GetReleaseById.ExecuteAsync(parameters.ReleaseId, cancellationToken);
|
IOperationResult<IGetReleaseByIdResult> result = await _client.GetReleaseById.ExecuteAsync(parameters.ReleaseId, cancellationToken);
|
||||||
Release = result.Data?.Release;
|
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()
|
public async Task DeleteRelease()
|
||||||
@ -81,6 +78,7 @@ public partial class SubmissionReleaseViewModel : RoutableScreen<ReleaseDetailPa
|
|||||||
.WithHorizontalPosition(HorizontalAlignment.Left)
|
.WithHorizontalPosition(HorizontalAlignment.Left)
|
||||||
.Show();
|
.Show();
|
||||||
|
|
||||||
|
HasChanges = false;
|
||||||
await Close();
|
await Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,25 +98,13 @@ public partial class SubmissionReleaseViewModel : RoutableScreen<ReleaseDetailPa
|
|||||||
.WithSeverity(NotificationSeverity.Success)
|
.WithSeverity(NotificationSeverity.Success)
|
||||||
.WithHorizontalPosition(HorizontalAlignment.Left)
|
.WithHorizontalPosition(HorizontalAlignment.Left)
|
||||||
.Show();
|
.Show();
|
||||||
|
|
||||||
|
HasChanges = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteDiscard()
|
private void ExecuteDiscard()
|
||||||
{
|
{
|
||||||
Changelog = Release?.Changelog ?? string.Empty;
|
Changelog = Release?.Changelog;
|
||||||
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)
|
|
||||||
{
|
|
||||||
Changelog = MarkdownDocument?.Text ?? string.Empty;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,7 +53,7 @@ public partial class WorkshopLibraryViewModel : RoutableHostScreen<RoutableScree
|
|||||||
public void GoBack()
|
public void GoBack()
|
||||||
{
|
{
|
||||||
if (ViewingDetails)
|
if (ViewingDetails)
|
||||||
_router.GoBack();
|
_router.Navigate("workshop/library/submissions");
|
||||||
else
|
else
|
||||||
_router.Navigate("workshop");
|
_router.Navigate("workshop");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
x:DataType="profile:ProfileDescriptionViewModel">
|
x:DataType="profile:ProfileDescriptionViewModel">
|
||||||
<StackPanel Spacing="10">
|
<StackPanel Spacing="10">
|
||||||
<Border Classes="card">
|
<Border Classes="card">
|
||||||
<mdxaml:MarkdownScrollViewer Markdown="{CompiledBinding Entry.Description}" MarkdownStyleName="FluentAvalonia">
|
<mdxaml:MarkdownScrollViewer Markdown="{CompiledBinding Entry.Description}" MarkdownStyleName="FluentAvalonia" Name="MarkdownScrollViewer" >
|
||||||
<mdxaml:MarkdownScrollViewer.Styles>
|
<mdxaml:MarkdownScrollViewer.Styles>
|
||||||
<StyleInclude Source="/Styles/Markdown.axaml" />
|
<StyleInclude Source="/Styles/Markdown.axaml" />
|
||||||
</mdxaml:MarkdownScrollViewer.Styles>
|
</mdxaml:MarkdownScrollViewer.Styles>
|
||||||
|
|||||||
@ -38,6 +38,7 @@ public class SubmissionWizardState : IDisposable
|
|||||||
public List<ImageUploadRequest> Images { get; set; } = new();
|
public List<ImageUploadRequest> Images { get; set; } = new();
|
||||||
|
|
||||||
public IEntrySource? EntrySource { get; set; }
|
public IEntrySource? EntrySource { get; set; }
|
||||||
|
public string? Changelog { get; set; }
|
||||||
|
|
||||||
public void ChangeScreen<TSubmissionViewModel>() where TSubmissionViewModel : SubmissionViewModel
|
public void ChangeScreen<TSubmissionViewModel>() where TSubmissionViewModel : SubmissionViewModel
|
||||||
{
|
{
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
<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:controls="clr-namespace:Artemis.UI.Controls"
|
||||||
|
xmlns:steps="clr-namespace:Artemis.UI.Screens.Workshop.SubmissionWizard.Steps"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.ChangelogStepView"
|
||||||
|
x:DataType="steps:ChangelogStepViewModel">
|
||||||
|
<Grid RowDefinitions="Auto,*">
|
||||||
|
<StackPanel Grid.Row="0">
|
||||||
|
<StackPanel.Styles>
|
||||||
|
<Styles>
|
||||||
|
<Style Selector="TextBlock">
|
||||||
|
<Setter Property="TextWrapping" Value="Wrap"></Setter>
|
||||||
|
</Style>
|
||||||
|
</Styles>
|
||||||
|
</StackPanel.Styles>
|
||||||
|
<TextBlock Theme="{StaticResource TitleTextBlockStyle}" TextWrapping="Wrap">Changelog</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
If you want to inform your users what has changed in this release, you can provide a changelog. This is optional but recommended.
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<controls:SplitMarkdownEditor Grid.Row="1" Markdown="{CompiledBinding Changelog}"/>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@ -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<ChangelogStepViewModel>
|
||||||
|
{
|
||||||
|
public ChangelogStepView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<UploadStepViewModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteGoBack()
|
||||||
|
{
|
||||||
|
State.Changelog = Changelog;
|
||||||
|
if (State.EntryType == EntryType.Layout)
|
||||||
|
State.ChangeScreen<LayoutInfoStepViewModel>();
|
||||||
|
else if (State.EntryType == EntryType.Plugin)
|
||||||
|
State.ChangeScreen<SpecificationsStepViewModel>();
|
||||||
|
else if (State.EntryType == EntryType.Profile)
|
||||||
|
State.ChangeScreen<ProfileAdaptionHintsStepViewModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -98,6 +98,6 @@ public partial class LayoutInfoStepViewModel : SubmissionViewModel
|
|||||||
if (State.EntryId == null)
|
if (State.EntryId == null)
|
||||||
State.ChangeScreen<SpecificationsStepViewModel>();
|
State.ChangeScreen<SpecificationsStepViewModel>();
|
||||||
else
|
else
|
||||||
State.ChangeScreen<UploadStepViewModel>();
|
State.ChangeScreen<ChangelogStepViewModel>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,6 +78,6 @@ public partial class PluginSelectionStepViewModel : SubmissionViewModel
|
|||||||
if (State.EntryId == null)
|
if (State.EntryId == null)
|
||||||
State.ChangeScreen<SpecificationsStepViewModel>();
|
State.ChangeScreen<SpecificationsStepViewModel>();
|
||||||
else
|
else
|
||||||
State.ChangeScreen<UploadStepViewModel>();
|
State.ChangeScreen<ChangelogStepViewModel>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,6 +64,6 @@ public class ProfileAdaptionHintsStepViewModel : SubmissionViewModel
|
|||||||
if (State.EntryId == null)
|
if (State.EntryId == null)
|
||||||
State.ChangeScreen<SpecificationsStepViewModel>();
|
State.ChangeScreen<SpecificationsStepViewModel>();
|
||||||
else
|
else
|
||||||
State.ChangeScreen<UploadStepViewModel>();
|
State.ChangeScreen<ChangelogStepViewModel>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,7 +70,7 @@ public partial class UploadStepViewModel : SubmissionViewModel
|
|||||||
|
|
||||||
// Create a release for the new entry
|
// Create a release for the new entry
|
||||||
IEntryUploadHandler uploadHandler = _entryUploadHandlerFactory.CreateHandler(State.EntryType);
|
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)
|
if (!uploadResult.IsSuccess)
|
||||||
throw new ArtemisWorkshopException(uploadResult.Message);
|
throw new ArtemisWorkshopException(uploadResult.Message);
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
using Artemis.UI.Shared.Utilities;
|
namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
||||||
|
|
||||||
namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
|
||||||
|
|
||||||
public interface IEntryUploadHandler
|
public interface IEntryUploadHandler
|
||||||
{
|
{
|
||||||
Task<EntryUploadResult> CreateReleaseAsync(long entryId, IEntrySource entrySource, CancellationToken cancellationToken);
|
Task<EntryUploadResult> CreateReleaseAsync(long entryId, IEntrySource entrySource, string? changelog, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
@ -18,7 +18,7 @@ public class LayoutEntryUploadHandler : IEntryUploadHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<EntryUploadResult> CreateReleaseAsync(long entryId, IEntrySource entrySource, CancellationToken cancellationToken)
|
public async Task<EntryUploadResult> CreateReleaseAsync(long entryId, IEntrySource entrySource, string? changelog, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (entrySource is not LayoutEntrySource source)
|
if (entrySource is not LayoutEntrySource source)
|
||||||
throw new InvalidOperationException("Can only create releases for layouts");
|
throw new InvalidOperationException("Can only create releases for layouts");
|
||||||
@ -62,6 +62,8 @@ public class LayoutEntryUploadHandler : IEntryUploadHandler
|
|||||||
MultipartFormDataContent content = new();
|
MultipartFormDataContent content = new();
|
||||||
StreamContent streamContent = new(archiveStream);
|
StreamContent streamContent = new(archiveStream);
|
||||||
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
|
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
|
||||||
|
if (!string.IsNullOrWhiteSpace(changelog))
|
||||||
|
content.Add(new StringContent(changelog), "Changelog");
|
||||||
content.Add(streamContent, "file", "file.zip");
|
content.Add(streamContent, "file", "file.zip");
|
||||||
|
|
||||||
// Submit
|
// Submit
|
||||||
|
|||||||
@ -14,7 +14,7 @@ public class PluginEntryUploadHandler : IEntryUploadHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<EntryUploadResult> CreateReleaseAsync(long entryId, IEntrySource entrySource, CancellationToken cancellationToken)
|
public async Task<EntryUploadResult> CreateReleaseAsync(long entryId, IEntrySource entrySource, string? changelog, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (entrySource is not PluginEntrySource source)
|
if (entrySource is not PluginEntrySource source)
|
||||||
throw new InvalidOperationException("Can only create releases for plugins");
|
throw new InvalidOperationException("Can only create releases for plugins");
|
||||||
@ -27,6 +27,8 @@ public class PluginEntryUploadHandler : IEntryUploadHandler
|
|||||||
MultipartFormDataContent content = new();
|
MultipartFormDataContent content = new();
|
||||||
StreamContent streamContent = new(fileStream);
|
StreamContent streamContent = new(fileStream);
|
||||||
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
|
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
|
||||||
|
if (!string.IsNullOrWhiteSpace(changelog))
|
||||||
|
content.Add(new StringContent(changelog), "Changelog");
|
||||||
content.Add(streamContent, "file", "file.zip");
|
content.Add(streamContent, "file", "file.zip");
|
||||||
|
|
||||||
// Submit
|
// Submit
|
||||||
|
|||||||
@ -17,7 +17,7 @@ public class ProfileEntryUploadHandler : IEntryUploadHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<EntryUploadResult> CreateReleaseAsync(long entryId, IEntrySource entrySource, CancellationToken cancellationToken)
|
public async Task<EntryUploadResult> CreateReleaseAsync(long entryId, IEntrySource entrySource, string? changelog, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (entrySource is not ProfileEntrySource source)
|
if (entrySource is not ProfileEntrySource source)
|
||||||
throw new InvalidOperationException("Can only create releases for profile configurations");
|
throw new InvalidOperationException("Can only create releases for profile configurations");
|
||||||
@ -32,6 +32,8 @@ public class ProfileEntryUploadHandler : IEntryUploadHandler
|
|||||||
StreamContent streamContent = new(archiveStream);
|
StreamContent streamContent = new(archiveStream);
|
||||||
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
|
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");
|
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");
|
content.Add(streamContent, "file", "file.zip");
|
||||||
|
|
||||||
// Submit
|
// Submit
|
||||||
|
|||||||
@ -2,10 +2,10 @@ namespace Artemis.WebClient.Workshop;
|
|||||||
|
|
||||||
public static class WorkshopConstants
|
public static class WorkshopConstants
|
||||||
{
|
{
|
||||||
public const string AUTHORITY_URL = "https://localhost:5001";
|
// public const string AUTHORITY_URL = "https://localhost:5001";
|
||||||
public const string WORKSHOP_URL = "https://localhost:7281";
|
// public const string WORKSHOP_URL = "https://localhost:7281";
|
||||||
// public const string AUTHORITY_URL = "https://identity.artemis-rgb.com";
|
public const string AUTHORITY_URL = "https://identity.artemis-rgb.com";
|
||||||
// public const string WORKSHOP_URL = "https://workshop.artemis-rgb.com";
|
public const string WORKSHOP_URL = "https://workshop.artemis-rgb.com";
|
||||||
public const string IDENTITY_CLIENT_NAME = "IdentityApiClient";
|
public const string IDENTITY_CLIENT_NAME = "IdentityApiClient";
|
||||||
public const string WORKSHOP_CLIENT_NAME = "WorkshopApiClient";
|
public const string WORKSHOP_CLIENT_NAME = "WorkshopApiClient";
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user