diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs index ff4394fde..15756d2a0 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IWindowService.cs @@ -16,7 +16,7 @@ public interface IWindowService : IArtemisSharedUIService /// /// The type of view model to create /// The created view model - TViewModel ShowWindow(params object[] parameters); + Window ShowWindow(out TViewModel viewModel, params object[] parameters); /// /// Given a ViewModel, show its corresponding View as a window diff --git a/src/Artemis.UI.Shared/Services/Window/WindowService.cs b/src/Artemis.UI.Shared/Services/Window/WindowService.cs index c30688eef..5db1716d2 100644 --- a/src/Artemis.UI.Shared/Services/Window/WindowService.cs +++ b/src/Artemis.UI.Shared/Services/Window/WindowService.cs @@ -21,14 +21,13 @@ internal class WindowService : IWindowService _container = container; } - public T ShowWindow(params object[] parameters) + public Window ShowWindow(out T viewModel, params object[] parameters) { - T viewModel = _container.Resolve(parameters); + viewModel = _container.Resolve(parameters); if (viewModel == null) throw new ArtemisSharedUIException($"Failed to show window for VM of type {typeof(T).Name}, could not create instance."); - ShowWindow(viewModel); - return viewModel; + return ShowWindow(viewModel); } public Window ShowWindow(object viewModel) diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index f7ffa8c34..fd7f60a06 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -20,6 +20,7 @@ + @@ -28,6 +29,7 @@ + @@ -43,9 +45,25 @@ + + + + + ProfileListView.axaml + Code + + + LayoutListView.axaml + Code + + + SubmissionsDetailView.axaml + Code + + \ No newline at end of file diff --git a/src/Artemis.UI/Routing/RouteViewModel.cs b/src/Artemis.UI/Routing/RouteViewModel.cs index 5ecc0f356..78cc458c9 100644 --- a/src/Artemis.UI/Routing/RouteViewModel.cs +++ b/src/Artemis.UI/Routing/RouteViewModel.cs @@ -4,18 +4,20 @@ namespace Artemis.UI.Routing; public class RouteViewModel { - public RouteViewModel(string path, string name) + public RouteViewModel(string name, string path, string? mathPath = null) { Path = path; Name = name; + MathPath = mathPath; } public string Path { get; } public string Name { get; } + public string? MathPath { get; } public bool Matches(string path) { - return path.StartsWith(Path, StringComparison.InvariantCultureIgnoreCase); + return path.StartsWith(MathPath ?? Path, StringComparison.InvariantCultureIgnoreCase); } /// diff --git a/src/Artemis.UI/Routing/Routes.cs b/src/Artemis.UI/Routing/Routes.cs index 128209f99..4ad88a88d 100644 --- a/src/Artemis.UI/Routing/Routes.cs +++ b/src/Artemis.UI/Routing/Routes.cs @@ -6,6 +6,8 @@ using Artemis.UI.Screens.Settings; using Artemis.UI.Screens.Settings.Updating; using Artemis.UI.Screens.SurfaceEditor; using Artemis.UI.Screens.Workshop; +using Artemis.UI.Screens.Workshop.Entries; +using Artemis.UI.Screens.Workshop.Entries.Tabs; using Artemis.UI.Screens.Workshop.Home; using Artemis.UI.Screens.Workshop.Layout; using Artemis.UI.Screens.Workshop.Library; @@ -24,16 +26,22 @@ public static class Routes #if DEBUG new RouteRegistration("workshop") { - Children = new List() + Children = new List { new RouteRegistration("offline/{message:string}"), - new RouteRegistration("profiles/{page:int}"), - new RouteRegistration("profiles/{entryId:guid}"), - new RouteRegistration("layouts/{page:int}"), - new RouteRegistration("layouts/{entryId:guid}"), + new RouteRegistration("entries") + { + Children = new List + { + new RouteRegistration("profiles/{page:int}"), + new RouteRegistration("profiles/details/{entryId:guid}"), + new RouteRegistration("layouts/{page:int}"), + new RouteRegistration("layouts/details/{entryId:guid}"), + } + }, new RouteRegistration("library") { - Children = new List() + Children = new List { new RouteRegistration("installed"), new RouteRegistration("submissions"), diff --git a/src/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Artemis.UI/Screens/Root/RootViewModel.cs index d057893e5..8b319b300 100644 --- a/src/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Artemis.UI/Screens/Root/RootViewModel.cs @@ -128,7 +128,7 @@ public class RootViewModel : RoutableHostScreen, IMainWindowProv private void ShowSplashScreen() { - _windowService.ShowWindow(); + _windowService.ShowWindow(out SplashViewModel _); } #region Tray commands diff --git a/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs index 63b97b1ab..26e8edcd2 100644 --- a/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/SettingsViewModel.cs @@ -21,11 +21,11 @@ public class SettingsViewModel : RoutableHostScreen, IMainScreen _router = router; SettingTabs = new ObservableCollection { - new("settings/general", "General"), - new("settings/plugins", "Plugins"), - new("settings/devices", "Devices"), - new("settings/releases", "Releases"), - new("settings/about", "About"), + new("General", "settings/general"), + new("Plugins", "settings/plugins"), + new("Devices", "settings/devices"), + new("Releases", "settings/releases"), + new("About", "settings/about"), }; // Navigate on tab change diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs index 6b7d6c035..0210d557f 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs @@ -41,8 +41,8 @@ public class SidebarViewModel : ActivatableViewModelBase #if DEBUG new(MaterialIconKind.TestTube, "Workshop", "workshop", null, new ObservableCollection { - new(MaterialIconKind.FolderVideo, "Profiles", "workshop/profiles/1", "workshop/profiles"), - new(MaterialIconKind.KeyboardVariant, "Layouts", "workshop/layouts/1", "workshop/layouts"), + new(MaterialIconKind.FolderVideo, "Profiles", "workshop/entries/profiles/1", "workshop/entries/profiles"), + new(MaterialIconKind.KeyboardVariant, "Layouts", "workshop/entries/layouts/1", "workshop/entries/layouts"), new(MaterialIconKind.Bookshelf, "Library", "workshop/library"), }), #endif diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntriesView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/EntriesView.axaml new file mode 100644 index 000000000..ecc131eb9 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntriesView.axaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntriesView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntriesView.axaml.cs new file mode 100644 index 000000000..3f2106645 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntriesView.axaml.cs @@ -0,0 +1,28 @@ +using System.Reactive.Disposables; +using Artemis.UI.Shared; +using Avalonia.ReactiveUI; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using ReactiveUI; +using System; + +namespace Artemis.UI.Screens.Workshop.Entries; + +public partial class EntriesView : ReactiveUserControl +{ + public EntriesView() + { + InitializeComponent(); + this.WhenActivated(d => { ViewModel.WhenAnyValue(vm => vm.Screen).WhereNotNull().Subscribe(Navigate).DisposeWith(d); }); + } + + private void Navigate(ViewModelBase viewModel) + { + Dispatcher.UIThread.Invoke(() => TabFrame.NavigateFromObject(viewModel)); + } + + private void NavigationView_OnBackRequested(object? sender, NavigationViewBackRequestedEventArgs e) + { + ViewModel?.GoBack(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs new file mode 100644 index 000000000..903a4d649 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntriesViewModel.cs @@ -0,0 +1,65 @@ +using System.Collections.ObjectModel; +using System.Linq; +using System.Reactive.Disposables; +using System.Threading; +using System.Threading.Tasks; +using Artemis.UI.Routing; +using Artemis.UI.Shared.Routing; +using ReactiveUI; +using System; +using System.Reactive.Linq; + +namespace Artemis.UI.Screens.Workshop.Entries; + +public class EntriesViewModel : RoutableHostScreen +{ + private readonly IRouter _router; + private RouteViewModel? _selectedTab; + private ObservableAsPropertyHelper? _viewingDetails; + + public EntriesViewModel(IRouter router) + { + _router = router; + + Tabs = new ObservableCollection + { + new("Profiles", "workshop/entries/profiles/1", "workshop/entries/profiles"), + new("Layouts", "workshop/entries/layouts/1", "workshop/entries/layouts") + }; + + this.WhenActivated(d => + { + // Show back button on details page + _viewingDetails = _router.CurrentPath.Select(p => p != null && p.Contains("details")).ToProperty(this, vm => vm.ViewingDetails).DisposeWith(d); + // Navigate on tab change + this.WhenAnyValue(vm => vm.SelectedTab) + .WhereNotNull() + .Subscribe(s => router.Navigate(s.Path, new RouterNavigationOptions {IgnoreOnPartialMatch = true})) + .DisposeWith(d); + }); + } + + public bool ViewingDetails => _viewingDetails?.Value ?? false; + public ObservableCollection Tabs { get; } + + public RouteViewModel? SelectedTab + { + get => _selectedTab; + set => RaiseAndSetIfChanged(ref _selectedTab, value); + } + + public override async Task OnNavigating(NavigationArguments args, CancellationToken cancellationToken) + { + SelectedTab = Tabs.FirstOrDefault(t => t.Matches(args.Path)); + if (SelectedTab == null) + await args.Router.Navigate(Tabs.First().Path); + } + + public void GoBack() + { + if (ViewingDetails) + _router.GoBack(); + else + _router.Navigate("workshop"); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntryListItemViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntryListItemViewModel.cs index a654b476e..4160048ff 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/EntryListItemViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntryListItemViewModel.cs @@ -33,10 +33,10 @@ public class EntryListItemViewModel : ActivatableViewModelBase switch (Entry.EntryType) { case EntryType.Layout: - await _router.Navigate($"workshop/layouts/{Entry.Id}"); + await _router.Navigate($"workshop/entries/layouts/details/{Entry.Id}"); break; case EntryType.Profile: - await _router.Navigate($"workshop/profiles/{Entry.Id}"); + await _router.Navigate($"workshop/entries/profiles/details/{Entry.Id}"); break; case EntryType.Plugin: break; diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntryListBaseViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntryListViewModel.cs similarity index 94% rename from src/Artemis.UI/Screens/Workshop/Entries/EntryListBaseViewModel.cs rename to src/Artemis.UI/Screens/Workshop/Entries/EntryListViewModel.cs index 970b966fb..ff4d2a138 100644 --- a/src/Artemis.UI/Screens/Workshop/Entries/EntryListBaseViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntryListViewModel.cs @@ -18,7 +18,7 @@ using StrawberryShake; namespace Artemis.UI.Screens.Workshop.Entries; -public abstract class EntryListBaseViewModel : RoutableScreen +public abstract class EntryListViewModel : RoutableScreen { private readonly INotificationService _notificationService; private readonly IWorkshopClient _workshopClient; @@ -31,7 +31,7 @@ public abstract class EntryListBaseViewModel : RoutableScreen getEntryListViewModel) { _workshopClient = workshopClient; @@ -143,6 +143,4 @@ public abstract class EntryListBaseViewModel : RoutableScreen null; } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntrySpecificationsView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/EntrySpecificationsView.axaml new file mode 100644 index 000000000..ea41ca8c5 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntrySpecificationsView.axaml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + Icon required + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + At least one category is required + + + + + + + + + + + + + Markdown supported, a better editor planned + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntrySpecificationsView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntrySpecificationsView.axaml.cs new file mode 100644 index 000000000..e8155b0de --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntrySpecificationsView.axaml.cs @@ -0,0 +1,34 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Media.Immutable; +using Avalonia.ReactiveUI; +using AvaloniaEdit.TextMate; +using TextMateSharp.Grammars; + +namespace Artemis.UI.Screens.Workshop.Entries; + +public partial class EntrySpecificationsView : ReactiveUserControl +{ + public EntrySpecificationsView() + { + InitializeComponent(); + + RegistryOptions options = new(ThemeName.Dark); + TextMate.Installation? install = DescriptionEditor.InstallTextMate(options); + + install.SetGrammar(options.GetScopeByExtension(".md")); + } + + #region Overrides of Visual + + /// + 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); + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/EntrySpecificationsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/EntrySpecificationsViewModel.cs new file mode 100644 index 000000000..a4b3f439c --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/EntrySpecificationsViewModel.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reactive; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Threading; +using System.Threading.Tasks; +using Artemis.UI.Extensions; +using Artemis.UI.Screens.Workshop.Categories; +using Artemis.UI.Screens.Workshop.Entries.Windows; +using Artemis.UI.Shared; +using Artemis.UI.Shared.Services; +using Artemis.WebClient.Workshop; +using Avalonia.Controls; +using AvaloniaEdit.Document; +using DynamicData; +using DynamicData.Aggregation; +using DynamicData.Binding; +using ReactiveUI; +using ReactiveUI.Validation.Extensions; +using ReactiveUI.Validation.Helpers; +using StrawberryShake; +using Bitmap = Avalonia.Media.Imaging.Bitmap; + +namespace Artemis.UI.Screens.Workshop.Entries; + +public class EntrySpecificationsViewModel : ValidatableViewModelBase +{ + private readonly IWindowService _windowService; + private ObservableAsPropertyHelper? _categoriesValid; + private ObservableAsPropertyHelper? _iconValid; + private string _description = string.Empty; + private string _name = string.Empty; + private string _summary = string.Empty; + private Bitmap? _iconBitmap; + private Window? _previewWindow; + private TextDocument? _markdownDocument; + + public EntrySpecificationsViewModel(IWorkshopClient workshopClient, IWindowService windowService) + { + _windowService = windowService; + SelectIcon = ReactiveCommand.CreateFromTask(ExecuteSelectIcon); + OpenMarkdownPreview = ReactiveCommand.Create(ExecuteOpenMarkdownPreview); + + // this.WhenAnyValue(vm => vm.Description).Subscribe(d => MarkdownDocument.Text = d); + this.WhenActivated(d => + { + // Load categories + Observable.FromAsync(workshopClient.GetCategories.ExecuteAsync).Subscribe(PopulateCategories).DisposeWith(d); + + this.ClearValidationRules(); + + MarkdownDocument = new TextDocument(new StringTextSource(Description)); + MarkdownDocument.TextChanged += MarkdownDocumentOnTextChanged; + Disposable.Create(() => + { + _previewWindow?.Close(); + MarkdownDocument.TextChanged -= MarkdownDocumentOnTextChanged; + MarkdownDocument = null; + ClearIcon(); + }).DisposeWith(d); + }); + } + + private void MarkdownDocumentOnTextChanged(object? sender, EventArgs e) + { + Description = MarkdownDocument.Text; + } + + public ReactiveCommand SelectIcon { get; } + public ReactiveCommand OpenMarkdownPreview { get; } + + public ObservableCollection Categories { get; } = new(); + public ObservableCollection Tags { get; } = new(); + public bool CategoriesValid => _categoriesValid?.Value ?? true; + public bool IconValid => _iconValid?.Value ?? true; + + public string Name + { + get => _name; + set => RaiseAndSetIfChanged(ref _name, value); + } + + public string Summary + { + get => _summary; + set => RaiseAndSetIfChanged(ref _summary, value); + } + + public string Description + { + get => _description; + set => RaiseAndSetIfChanged(ref _description, value); + } + + public Bitmap? IconBitmap + { + get => _iconBitmap; + set => RaiseAndSetIfChanged(ref _iconBitmap, value); + } + + public TextDocument? MarkdownDocument + { + get => _markdownDocument; + set => RaiseAndSetIfChanged(ref _markdownDocument, value); + } + + public List PreselectedCategories { get; set; } = new List(); + + public void SetupDataValidation() + { + // Hopefully this can be avoided in the future + // https://github.com/reactiveui/ReactiveUI.Validation/discussions/558 + this.ValidationRule(vm => vm.Name, s => !string.IsNullOrWhiteSpace(s), "Name is required"); + this.ValidationRule(vm => vm.Summary, s => !string.IsNullOrWhiteSpace(s), "Summary is required"); + this.ValidationRule(vm => vm.Description, s => !string.IsNullOrWhiteSpace(s), "Description is required"); + + // These don't use inputs that support validation messages, do so manually + ValidationHelper iconRule = this.ValidationRule(vm => vm.IconBitmap, s => s != null, "Icon required"); + ValidationHelper categoriesRule = this.ValidationRule(vm => vm.Categories, Categories.ToObservableChangeSet().AutoRefresh(c => c.IsSelected).Filter(c => c.IsSelected).IsNotEmpty(), + "At least one category must be selected" + ); + _iconValid = iconRule.ValidationChanged.Select(c => c.IsValid).ToProperty(this, vm => vm.IconValid); + _categoriesValid = categoriesRule.ValidationChanged.Select(c => c.IsValid).ToProperty(this, vm => vm.CategoriesValid); + } + + private async Task ExecuteSelectIcon() + { + string[]? result = await _windowService.CreateOpenFileDialog() + .HavingFilter(f => f.WithExtension("png").WithExtension("jpg").WithExtension("bmp").WithName("Bitmap image")) + .ShowAsync(); + + if (result == null) + return; + + IconBitmap?.Dispose(); + IconBitmap = BitmapExtensions.LoadAndResize(result[0], 128); + } + + private void ExecuteOpenMarkdownPreview() + { + if (_previewWindow != null) + { + _previewWindow.Activate(); + return; + } + + _previewWindow = _windowService.ShowWindow(out MarkdownPreviewViewModel _, this.WhenAnyValue(vm => vm.Description)); + _previewWindow.Closed += PreviewWindowOnClosed; + } + + private void PreviewWindowOnClosed(object? sender, EventArgs e) + { + if (_previewWindow != null) + _previewWindow.Closed -= PreviewWindowOnClosed; + _previewWindow = null; + } + + private void ClearIcon() + { + IconBitmap?.Dispose(); + IconBitmap = null; + } + + private void PopulateCategories(IOperationResult result) + { + Categories.Clear(); + if (result.Data != null) + Categories.AddRange(result.Data.Categories.Select(c => new CategoryViewModel(c) {IsSelected = PreselectedCategories.Contains(c.Id)})); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml new file mode 100644 index 000000000..575f24ffc --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml @@ -0,0 +1,41 @@ + + + + + + Categories + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml.cs similarity index 77% rename from src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml.cs rename to src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml.cs index dff088223..6b574bb29 100644 --- a/src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListView.axaml.cs @@ -1,6 +1,6 @@ using Avalonia.ReactiveUI; -namespace Artemis.UI.Screens.Workshop.Layout; +namespace Artemis.UI.Screens.Workshop.Entries.Tabs; public partial class LayoutListView : ReactiveUserControl { diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs similarity index 85% rename from src/Artemis.UI/Screens/Workshop/Layout/LayoutListViewModel.cs rename to src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs index 8f5b108e7..a32408d09 100644 --- a/src/Artemis.UI/Screens/Workshop/Layout/LayoutListViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/LayoutListViewModel.cs @@ -1,13 +1,12 @@ using System; using Artemis.UI.Screens.Workshop.Categories; -using Artemis.UI.Screens.Workshop.Entries; using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Services; using Artemis.WebClient.Workshop; -namespace Artemis.UI.Screens.Workshop.Layout; +namespace Artemis.UI.Screens.Workshop.Entries.Tabs; -public class LayoutListViewModel : EntryListBaseViewModel +public class LayoutListViewModel : EntryListViewModel { /// public LayoutListViewModel(IWorkshopClient workshopClient, @@ -24,7 +23,7 @@ public class LayoutListViewModel : EntryListBaseViewModel /// protected override string GetPagePath(int page) { - return $"workshop/layouts/{page}"; + return $"workshop/entries/layouts/{page}"; } /// diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml new file mode 100644 index 000000000..e7b733741 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml @@ -0,0 +1,42 @@ + + + + + + Categories + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml.cs similarity index 78% rename from src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml.cs rename to src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml.cs index 6c55237f5..62adad88b 100644 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListView.axaml.cs @@ -1,6 +1,6 @@ using Avalonia.ReactiveUI; -namespace Artemis.UI.Screens.Workshop.Profile; +namespace Artemis.UI.Screens.Workshop.Entries.Tabs; public partial class ProfileListView : ReactiveUserControl { diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs similarity index 84% rename from src/Artemis.UI/Screens/Workshop/Profile/ProfileListViewModel.cs rename to src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs index 521c9a351..49df897db 100644 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileListViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Entries/Tabs/ProfileListViewModel.cs @@ -1,13 +1,12 @@ using System; using Artemis.UI.Screens.Workshop.Categories; -using Artemis.UI.Screens.Workshop.Entries; using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Services; using Artemis.WebClient.Workshop; -namespace Artemis.UI.Screens.Workshop.Profile; +namespace Artemis.UI.Screens.Workshop.Entries.Tabs; -public class ProfileListViewModel : EntryListBaseViewModel +public class ProfileListViewModel : EntryListViewModel { /// public ProfileListViewModel(IWorkshopClient workshopClient, @@ -24,7 +23,7 @@ public class ProfileListViewModel : EntryListBaseViewModel /// protected override string GetPagePath(int page) { - return $"workshop/profiles/{page}"; + return $"workshop/entries/profiles/{page}"; } /// diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Windows/MarkdownPreviewView.axaml b/src/Artemis.UI/Screens/Workshop/Entries/Windows/MarkdownPreviewView.axaml new file mode 100644 index 000000000..8898bef0a --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/Windows/MarkdownPreviewView.axaml @@ -0,0 +1,43 @@ + + + + Markdown Previewer + + In this window you can preview the Markdown you're writing in the main window of the application. + + The preview updates realtime, so it might be a good idea to keep this window visible while you're typing. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Windows/MarkdownPreviewView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Entries/Windows/MarkdownPreviewView.axaml.cs new file mode 100644 index 000000000..ec5b62868 --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/Windows/MarkdownPreviewView.axaml.cs @@ -0,0 +1,12 @@ + +using Artemis.UI.Shared; + +namespace Artemis.UI.Screens.Workshop.Entries.Windows; + +public partial class MarkdownPreviewView : ReactiveAppWindow +{ + public MarkdownPreviewView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Entries/Windows/MarkdownPreviewViewModel.cs b/src/Artemis.UI/Screens/Workshop/Entries/Windows/MarkdownPreviewViewModel.cs new file mode 100644 index 000000000..4e8680d0e --- /dev/null +++ b/src/Artemis.UI/Screens/Workshop/Entries/Windows/MarkdownPreviewViewModel.cs @@ -0,0 +1,21 @@ +using System; +using Artemis.UI.Shared; + +namespace Artemis.UI.Screens.Workshop.Entries.Windows; + +public class MarkdownPreviewViewModel : ActivatableViewModelBase +{ + public event EventHandler? Closed; + + public IObservable Markdown { get; } + + public MarkdownPreviewViewModel(IObservable markdown) + { + Markdown = markdown; + } + + protected virtual void OnClosed() + { + Closed?.Invoke(this, EventArgs.Empty); + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml b/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml index c3b4593f6..433f87c50 100644 --- a/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml @@ -41,7 +41,7 @@ - - + - - + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs index 3bd224a6a..e559e7e09 100644 --- a/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutDetailsViewModel.cs @@ -1,10 +1,17 @@ using System; +using System.Reactive; +using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; +using Artemis.Core; using Artemis.UI.Screens.Workshop.Parameters; -using Artemis.UI.Shared; using Artemis.UI.Shared.Routing; +using Artemis.UI.Shared.Services; +using Artemis.UI.Shared.Services.Builders; +using Artemis.UI.Shared.Utilities; using Artemis.WebClient.Workshop; +using Artemis.WebClient.Workshop.DownloadHandlers; +using ReactiveUI; using StrawberryShake; namespace Artemis.UI.Screens.Workshop.Layout; @@ -12,19 +19,29 @@ namespace Artemis.UI.Screens.Workshop.Layout; public class LayoutDetailsViewModel : RoutableScreen { private readonly IWorkshopClient _client; + private readonly INotificationService _notificationService; + private readonly IWindowService _windowService; + private readonly ObservableAsPropertyHelper _updatedAt; private IGetEntryById_Entry? _entry; - public LayoutDetailsViewModel(IWorkshopClient client) + public LayoutDetailsViewModel(IWorkshopClient client, INotificationService notificationService, IWindowService windowService) { _client = client; + _notificationService = notificationService; + _windowService = windowService; + _updatedAt = this.WhenAnyValue(vm => vm.Entry).Select(e => e?.LatestRelease?.CreatedAt ?? e?.CreatedAt).ToProperty(this, vm => vm.UpdatedAt); + + DownloadLatestRelease = ReactiveCommand.CreateFromTask(ExecuteDownloadLatestRelease); } - public EntryType? EntryType => null; + public ReactiveCommand DownloadLatestRelease { get; } + + public DateTimeOffset? UpdatedAt => _updatedAt.Value; public IGetEntryById_Entry? Entry { get => _entry; - set => RaiseAndSetIfChanged(ref _entry, value); + private set => RaiseAndSetIfChanged(ref _entry, value); } public override async Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken) @@ -40,4 +57,9 @@ public class LayoutDetailsViewModel : RoutableScreen Entry = result.Data?.Entry; } + + private Task ExecuteDownloadLatestRelease(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml b/src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml deleted file mode 100644 index 77801f79e..000000000 --- a/src/Artemis.UI/Screens/Workshop/Layout/LayoutListView.axaml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - Categories - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsDetailView.axaml b/src/Artemis.UI/Screens/Workshop/Library/SubmissionsDetailView.axaml similarity index 53% rename from src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsDetailView.axaml rename to src/Artemis.UI/Screens/Workshop/Library/SubmissionsDetailView.axaml index 1a4585317..ca36904b6 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsDetailView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionsDetailView.axaml @@ -2,7 +2,9 @@ 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.Tabs.SubmissionsDetailView"> - Welcome to Avalonia! + x:Class="Artemis.UI.Screens.Workshop.Library.SubmissionsDetailView" + x:DataType="library:SubmissionsDetailViewModel"> + diff --git a/src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsDetailView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionsDetailView.axaml.cs similarity index 62% rename from src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsDetailView.axaml.cs rename to src/Artemis.UI/Screens/Workshop/Library/SubmissionsDetailView.axaml.cs index 1af01b4d4..bca7ef914 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsDetailView.axaml.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionsDetailView.axaml.cs @@ -1,9 +1,6 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; -namespace Artemis.UI.Screens.Workshop.Library.Tabs; +namespace Artemis.UI.Screens.Workshop.Library; public partial class SubmissionsDetailView : ReactiveUserControl { diff --git a/src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsDetailViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/SubmissionsDetailViewModel.cs similarity index 57% rename from src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsDetailViewModel.cs rename to src/Artemis.UI/Screens/Workshop/Library/SubmissionsDetailViewModel.cs index b81f5a368..283a490e7 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsDetailViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/SubmissionsDetailViewModel.cs @@ -1,13 +1,21 @@ using System; using System.Threading; using System.Threading.Tasks; +using Artemis.UI.Screens.Workshop.Entries; using Artemis.UI.Screens.Workshop.Parameters; using Artemis.UI.Shared.Routing; -namespace Artemis.UI.Screens.Workshop.Library.Tabs; +namespace Artemis.UI.Screens.Workshop.Library; public class SubmissionsDetailViewModel : RoutableScreen { + public EntrySpecificationsViewModel EntrySpecificationsViewModel { get; } + + public SubmissionsDetailViewModel(EntrySpecificationsViewModel entrySpecificationsViewModel) + { + EntrySpecificationsViewModel = entrySpecificationsViewModel; + } + public override Task OnNavigating(WorkshopDetailParameters parameters, NavigationArguments args, CancellationToken cancellationToken) { Console.WriteLine(parameters.EntryId); diff --git a/src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsTabViewModel.cs b/src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsTabViewModel.cs index 719f56353..ebcf95fce 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Library/Tabs/SubmissionsTabViewModel.cs @@ -21,6 +21,7 @@ public class SubmissionsTabViewModel : RoutableScreen private readonly IWorkshopClient _client; private readonly SourceCache _entries; private readonly IWindowService _windowService; + private readonly IRouter _router; private bool _isLoading = true; private bool _workshopReachable; @@ -28,6 +29,7 @@ public class SubmissionsTabViewModel : RoutableScreen { _client = client; _windowService = windowService; + _router = router; _entries = new SourceCache(e => e.Id); _entries.Connect().Bind(out ReadOnlyObservableCollection entries).Subscribe(); @@ -76,9 +78,9 @@ public class SubmissionsTabViewModel : RoutableScreen await _windowService.ShowDialogAsync(); } - private Task ExecuteNavigateToEntry(IGetSubmittedEntries_SubmittedEntries entry, CancellationToken cancellationToken) + private async Task ExecuteNavigateToEntry(IGetSubmittedEntries_SubmittedEntries entry, CancellationToken cancellationToken) { - return Task.CompletedTask; + await _router.Navigate($"workshop/library/submissions/{entry.Id}"); } private async Task GetEntries(CancellationToken ct) @@ -103,6 +105,4 @@ public class SubmissionsTabViewModel : RoutableScreen IsLoading = false; } } - - public EntryType? EntryType => null; } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Library/WorkshopLibraryVIew.axaml b/src/Artemis.UI/Screens/Workshop/Library/WorkshopLibraryVIew.axaml index 1dafa387c..0c9eff517 100644 --- a/src/Artemis.UI/Screens/Workshop/Library/WorkshopLibraryVIew.axaml +++ b/src/Artemis.UI/Screens/Workshop/Library/WorkshopLibraryVIew.axaml @@ -8,7 +8,13 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Workshop.Library.WorkshopLibraryView" x:DataType="library:WorkshopLibraryViewModel"> - + - - - - - - - - - - - - - - - - - - - - Icon required - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - At least one category is required - - - - - - - - - - Markdown supported, a better editor planned - + + - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/SpecificationsStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/SpecificationsStepViewModel.cs index 495e5d591..ebb75cd42 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/SpecificationsStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/SpecificationsStepViewModel.cs @@ -1,93 +1,42 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Reactive; using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Threading.Tasks; using Artemis.UI.Extensions; -using Artemis.UI.Screens.Workshop.Categories; +using Artemis.UI.Screens.Workshop.Entries; using Artemis.UI.Screens.Workshop.SubmissionWizard.Steps.Profile; -using Artemis.UI.Shared.Services; using Artemis.WebClient.Workshop; using Avalonia.Threading; using DynamicData; -using DynamicData.Aggregation; -using DynamicData.Binding; using ReactiveUI; using ReactiveUI.Validation.Extensions; -using ReactiveUI.Validation.Helpers; -using StrawberryShake; -using Bitmap = Avalonia.Media.Imaging.Bitmap; namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps; public class SpecificationsStepViewModel : SubmissionViewModel { - private readonly IWindowService _windowService; - private ObservableAsPropertyHelper? _categoriesValid; - private ObservableAsPropertyHelper? _iconValid; - private string _description = string.Empty; - private string _name = string.Empty; - private string _summary = string.Empty; - private Bitmap? _iconBitmap; - - public SpecificationsStepViewModel(IWorkshopClient workshopClient, IWindowService windowService) + public SpecificationsStepViewModel(EntrySpecificationsViewModel entrySpecificationsViewModel) { - _windowService = windowService; + EntrySpecificationsViewModel = entrySpecificationsViewModel; GoBack = ReactiveCommand.Create(ExecuteGoBack); - Continue = ReactiveCommand.Create(ExecuteContinue, ValidationContext.Valid); - SelectIcon = ReactiveCommand.CreateFromTask(ExecuteSelectIcon); + Continue = ReactiveCommand.Create(ExecuteContinue, EntrySpecificationsViewModel.ValidationContext.Valid); - this.WhenActivated(d => + this.WhenActivated((CompositeDisposable d) => { DisplayName = $"{State.EntryType} Information"; - // Load categories - Observable.FromAsync(workshopClient.GetCategories.ExecuteAsync).Subscribe(PopulateCategories).DisposeWith(d); - // Apply the state ApplyFromState(); - this.ClearValidationRules(); - Disposable.Create(ClearIcon).DisposeWith(d); + EntrySpecificationsViewModel.ClearValidationRules(); }); } + public EntrySpecificationsViewModel EntrySpecificationsViewModel { get; } public override ReactiveCommand Continue { get; } public override ReactiveCommand GoBack { get; } - public ReactiveCommand SelectIcon { get; } - - public ObservableCollection Categories { get; } = new(); - public ObservableCollection Tags { get; } = new(); - public bool CategoriesValid => _categoriesValid?.Value ?? true; - public bool IconValid => _iconValid?.Value ?? true; - - public string Name - { - get => _name; - set => RaiseAndSetIfChanged(ref _name, value); - } - - public string Summary - { - get => _summary; - set => RaiseAndSetIfChanged(ref _summary, value); - } - - public string Description - { - get => _description; - set => RaiseAndSetIfChanged(ref _description, value); - } - - public Bitmap? IconBitmap - { - get => _iconBitmap; - set => RaiseAndSetIfChanged(ref _iconBitmap, value); - } private void ExecuteGoBack() { @@ -110,101 +59,61 @@ public class SpecificationsStepViewModel : SubmissionViewModel private void ExecuteContinue() { - if (!ValidationContext.Validations.Any()) + if (!EntrySpecificationsViewModel.ValidationContext.Validations.Any()) { // The ValidationContext seems to update asynchronously, so stop and schedule a retry - SetupDataValidation(); + EntrySpecificationsViewModel.SetupDataValidation(); Dispatcher.UIThread.Post(ExecuteContinue); return; } ApplyToState(); - - if (!ValidationContext.GetIsValid()) + + if (!EntrySpecificationsViewModel.ValidationContext.GetIsValid()) return; - + State.ChangeScreen(); } - private async Task ExecuteSelectIcon() - { - string[]? result = await _windowService.CreateOpenFileDialog() - .HavingFilter(f => f.WithExtension("png").WithExtension("jpg").WithExtension("bmp").WithName("Bitmap image")) - .ShowAsync(); - - if (result == null) - return; - - IconBitmap?.Dispose(); - IconBitmap = BitmapExtensions.LoadAndResize(result[0], 128); - } - - private void ClearIcon() - { - IconBitmap?.Dispose(); - IconBitmap = null; - } - - private void PopulateCategories(IOperationResult result) - { - Categories.Clear(); - if (result.Data != null) - Categories.AddRange(result.Data.Categories.Select(c => new CategoryViewModel(c) {IsSelected = State.Categories.Contains(c.Id)})); - } - - private void SetupDataValidation() - { - // Hopefully this can be avoided in the future - // https://github.com/reactiveui/ReactiveUI.Validation/discussions/558 - this.ValidationRule(vm => vm.Name, s => !string.IsNullOrWhiteSpace(s), "Name is required"); - this.ValidationRule(vm => vm.Summary, s => !string.IsNullOrWhiteSpace(s), "Summary is required"); - this.ValidationRule(vm => vm.Description, s => !string.IsNullOrWhiteSpace(s), "Description is required"); - - // These don't use inputs that support validation messages, do so manually - ValidationHelper iconRule = this.ValidationRule(vm => vm.IconBitmap, s => s != null, "Icon required"); - ValidationHelper categoriesRule = this.ValidationRule(vm => vm.Categories, Categories.ToObservableChangeSet().AutoRefresh(c => c.IsSelected).Filter(c => c.IsSelected).IsNotEmpty(), - "At least one category must be selected" - ); - _iconValid = iconRule.ValidationChanged.Select(c => c.IsValid).ToProperty(this, vm => vm.IconValid); - _categoriesValid = categoriesRule.ValidationChanged.Select(c => c.IsValid).ToProperty(this, vm => vm.CategoriesValid); - } - private void ApplyFromState() { // Basic fields - Name = State.Name; - Summary = State.Summary; - Description = State.Description; + EntrySpecificationsViewModel.Name = State.Name; + EntrySpecificationsViewModel.Summary = State.Summary; + EntrySpecificationsViewModel.Description = State.Description; // Tags - Tags.Clear(); - Tags.AddRange(State.Tags); + EntrySpecificationsViewModel.Tags.Clear(); + EntrySpecificationsViewModel.Tags.AddRange(State.Tags); + + // Categories + EntrySpecificationsViewModel.PreselectedCategories = State.Categories; // Icon if (State.Icon != null) { State.Icon.Seek(0, SeekOrigin.Begin); - IconBitmap = BitmapExtensions.LoadAndResize(State.Icon, 128); + EntrySpecificationsViewModel.IconBitmap = BitmapExtensions.LoadAndResize(State.Icon, 128); } } private void ApplyToState() { // Basic fields - State.Name = Name; - State.Summary = Summary; - State.Description = Description; + State.Name = EntrySpecificationsViewModel.Name; + State.Summary = EntrySpecificationsViewModel.Summary; + State.Description = EntrySpecificationsViewModel.Description; // Categories and tasks - State.Categories = Categories.Where(c => c.IsSelected).Select(c => c.Id).ToList(); - State.Tags = new List(Tags); + State.Categories = EntrySpecificationsViewModel.Categories.Where(c => c.IsSelected).Select(c => c.Id).ToList(); + State.Tags = new List(EntrySpecificationsViewModel.Tags); // Icon State.Icon?.Dispose(); - if (IconBitmap != null) + if (EntrySpecificationsViewModel.IconBitmap != null) { State.Icon = new MemoryStream(); - IconBitmap.Save(State.Icon); + EntrySpecificationsViewModel.IconBitmap.Save(State.Icon); } else { diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs index 14e56958a..fc726e908 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs @@ -152,13 +152,13 @@ public class UploadStepViewModel : SubmissionViewModel switch (State.EntryType) { case EntryType.Layout: - await _router.Navigate($"workshop/layouts/{_entryId.Value}"); + await _router.Navigate($"workshop/entries/layouts/{_entryId.Value}"); break; case EntryType.Plugin: - await _router.Navigate($"workshop/plugins/{_entryId.Value}"); + await _router.Navigate($"workshop/entries/plugins/{_entryId.Value}"); break; case EntryType.Profile: - await _router.Navigate($"workshop/profiles/{_entryId.Value}"); + await _router.Navigate($"workshop/entries/profiles/{_entryId.Value}"); break; default: throw new ArgumentOutOfRangeException(); diff --git a/src/Artemis.UI/Services/DebugService.cs b/src/Artemis.UI/Services/DebugService.cs index 138bfa994..62b644f29 100644 --- a/src/Artemis.UI/Services/DebugService.cs +++ b/src/Artemis.UI/Services/DebugService.cs @@ -22,7 +22,8 @@ public class DebugService : IDebugService private void CreateDebugger() { - _debugViewModel = _windowService.ShowWindow(); + _windowService.ShowWindow(out DebugViewModel debugViewModel); + _debugViewModel = debugViewModel; } public void ClearDebugger() diff --git a/src/Artemis.UI/Styles/Artemis.axaml b/src/Artemis.UI/Styles/Artemis.axaml index 0bd25e4aa..a4ac040f9 100644 --- a/src/Artemis.UI/Styles/Artemis.axaml +++ b/src/Artemis.UI/Styles/Artemis.axaml @@ -1,7 +1,9 @@  + xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" + xmlns:aedit="using:AvaloniaEdit" + xmlns:aedit2="using:AvaloniaEdit.Editing"> @@ -9,6 +11,18 @@ + + + + + + +