1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 13:28:33 +00:00

Replace profile-from-workshop-dialog with a page

This commit is contained in:
Robert 2025-12-10 23:06:14 +01:00
parent 638dbd11eb
commit 108cbaae3d
14 changed files with 213 additions and 44 deletions

View File

@ -103,7 +103,9 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
{ {
if (_root == null) if (_root == null)
throw new ArtemisRoutingException("Cannot navigate without a root having been set"); throw new ArtemisRoutingException("Cannot navigate without a root having been set");
if (PathEquals(path, options) || (_currentNavigation != null && _currentNavigation.PathEquals(path, options)))
// If navigating to the same path, don't do anything
if ((_currentNavigation == null && PathEquals(path, options)) || (_currentNavigation != null && _currentNavigation.PathEquals(path, options)))
return; return;
string? previousPath = _currentRouteSubject.Value; string? previousPath = _currentRouteSubject.Value;
@ -128,12 +130,8 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
await navigation.Navigate(args); await navigation.Navigate(args);
// If it was cancelled before completion, don't add it to history or update the current path // If it was cancelled before completion, don't add it to history or update the current path
// Do reload the current path because it may have been partially navigated away from
if (navigation.Cancelled) if (navigation.Cancelled)
{
await Reload();
return; return;
}
if (options.AddToHistory && previousPath != null) if (options.AddToHistory && previousPath != null)
{ {

View File

@ -64,7 +64,10 @@ namespace Artemis.UI.Routing
new RouteRegistration<AccountTabViewModel>("account"), new RouteRegistration<AccountTabViewModel>("account"),
new RouteRegistration<AboutTabViewModel>("about") new RouteRegistration<AboutTabViewModel>("about")
]), ]),
new RouteRegistration<ProfileEditorViewModel>("profile-editor/{profileConfigurationId:guid}") new RouteRegistration<ProfileViewModel>("profile/{profileConfigurationId:guid}", [
new RouteRegistration<ProfileEditorViewModel>("editor"),
new RouteRegistration<WorkshopProfileViewModel>("workshop")
]),
]; ];
} }
} }

View File

@ -15,11 +15,8 @@ using Artemis.UI.Screens.ProfileEditor.StatusBar;
using Artemis.UI.Screens.ProfileEditor.VisualEditor; using Artemis.UI.Screens.ProfileEditor.VisualEditor;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.MainWindow; using Artemis.UI.Shared.Services.MainWindow;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.WebClient.Workshop.Models;
using Artemis.WebClient.Workshop.Services;
using DynamicData; using DynamicData;
using DynamicData.Binding; using DynamicData.Binding;
using PropertyChanged.SourceGenerator; using PropertyChanged.SourceGenerator;
@ -27,14 +24,12 @@ using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor; namespace Artemis.UI.Screens.ProfileEditor;
public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewModelParameters>, IMainScreenViewModel public partial class ProfileEditorViewModel : RoutableScreen<ProfileViewModelParameters>, IMainScreenViewModel
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly IMainWindowService _mainWindowService; private readonly IMainWindowService _mainWindowService;
private readonly IWorkshopService _workshopService;
private readonly IWindowService _windowService;
private readonly SourceList<IToolViewModel> _tools; private readonly SourceList<IToolViewModel> _tools;
private ObservableAsPropertyHelper<ProfileEditorHistory?>? _history; private ObservableAsPropertyHelper<ProfileEditorHistory?>? _history;
private ObservableAsPropertyHelper<bool>? _suspendedEditing; private ObservableAsPropertyHelper<bool>? _suspendedEditing;
@ -53,16 +48,12 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
StatusBarViewModel statusBarViewModel, StatusBarViewModel statusBarViewModel,
IEnumerable<IToolViewModel> toolViewModels, IEnumerable<IToolViewModel> toolViewModels,
IMainWindowService mainWindowService, IMainWindowService mainWindowService,
IInputService inputService, IInputService inputService)
IWorkshopService workshopService,
IWindowService windowService)
{ {
_profileService = profileService; _profileService = profileService;
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_settingsService = settingsService; _settingsService = settingsService;
_mainWindowService = mainWindowService; _mainWindowService = mainWindowService;
_workshopService = workshopService;
_windowService = windowService;
_tools = new SourceList<IToolViewModel>(); _tools = new SourceList<IToolViewModel>();
_tools.AddRange(toolViewModels); _tools.AddRange(toolViewModels);
@ -75,6 +66,7 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
Tools = tools; Tools = tools;
visualEditorViewModel.SetTools(_tools); visualEditorViewModel.SetTools(_tools);
ParameterSource = ParameterSource.Route;
StatusBarViewModel = statusBarViewModel; StatusBarViewModel = statusBarViewModel;
VisualEditorViewModel = visualEditorViewModel; VisualEditorViewModel = visualEditorViewModel;
ProfileTreeViewModel = profileTreeViewModel; ProfileTreeViewModel = profileTreeViewModel;
@ -193,7 +185,7 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
#region Overrides of RoutableScreen<object,ProfileEditorViewModelParameters> #region Overrides of RoutableScreen<object,ProfileEditorViewModelParameters>
/// <inheritdoc /> /// <inheritdoc />
public override async Task OnNavigating(ProfileEditorViewModelParameters parameters, NavigationArguments args, CancellationToken cancellationToken) public override async Task OnNavigating(ProfileViewModelParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
{ {
ProfileConfiguration? profileConfiguration = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == parameters.ProfileId); ProfileConfiguration? profileConfiguration = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == parameters.ProfileId);
@ -204,23 +196,6 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
return; return;
} }
// If the profile is from the workshop, warn the user that auto-updates will be disabled
InstalledEntry? workshopEntry = _workshopService.GetInstalledEntryByProfile(profileConfiguration);
if (workshopEntry != null && workshopEntry.AutoUpdate)
{
bool confirmed = await _windowService.ShowConfirmContentDialog(
"Editing a workshop profile",
"You are about to edit a profile from the workshop, to preserve your changes auto-updating will be disabled.",
"Disable auto-update");
if (confirmed)
_workshopService.SetAutoUpdate(workshopEntry, false);
else
{
args.Cancel();
return;
}
}
await _profileEditorService.ChangeCurrentProfileConfiguration(profileConfiguration); await _profileEditorService.ChangeCurrentProfileConfiguration(profileConfiguration);
ProfileConfiguration = profileConfiguration; ProfileConfiguration = profileConfiguration;
} }
@ -236,9 +211,4 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
} }
#endregion #endregion
}
public class ProfileEditorViewModelParameters
{
public Guid ProfileId { get; set; }
} }

View File

@ -0,0 +1,14 @@
<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:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ui="clr-namespace:Artemis.UI"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileView">
<controls:Frame Name="RouterFrame" IsNavigationStackEnabled="False" CacheSize="0">
<controls:Frame.NavigationPageFactory>
<ui:PageFactory/>
</controls:Frame.NavigationPageFactory>
</controls:Frame>
</UserControl>

View File

@ -0,0 +1,19 @@
using System;
using System.Reactive.Disposables;
using Artemis.UI.Shared.Routing;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor;
public partial class ProfileView : ReactiveUserControl<ProfileViewModel>
{
public ProfileView()
{
InitializeComponent();
this.WhenActivated(d => ViewModel.WhenAnyValue(vm => vm.Screen)
.WhereNotNull()
.Subscribe(screen => RouterFrame.NavigateFromObject(screen))
.DisposeWith(d));
}
}

View File

@ -0,0 +1,62 @@
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Workshop.Models;
using Artemis.WebClient.Workshop.Services;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor;
public class ProfileViewModel : RoutableHostScreen<RoutableScreen, ProfileViewModelParameters>, IMainScreenViewModel
{
private readonly IProfileService _profileService;
private readonly IWorkshopService _workshopService;
private readonly ObservableAsPropertyHelper<ViewModelBase?> _titleBarViewModel;
public ProfileViewModel(IProfileService profileService, IWorkshopService workshopService)
{
_profileService = profileService;
_workshopService = workshopService;
_titleBarViewModel = this.WhenAnyValue(vm => vm.Screen).Select(screen => screen as IMainScreenViewModel)
.Select(mainScreen => mainScreen?.TitleBarViewModel)
.ToProperty(this, vm => vm.TitleBarViewModel);
}
public ViewModelBase? TitleBarViewModel => _titleBarViewModel.Value;
/// <inheritdoc />
public override async Task OnNavigating(ProfileViewModelParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
{
ProfileConfiguration? profileConfiguration = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == parameters.ProfileId);
// If the profile doesn't exist, cancel navigation
if (profileConfiguration == null)
{
args.Cancel();
return;
}
// If the profile is from the workshop, redirect to the workshop page
InstalledEntry? workshopEntry = _workshopService.GetInstalledEntryByProfile(profileConfiguration);
if (workshopEntry != null && workshopEntry.AutoUpdate)
{
if (!args.Path.EndsWith("workshop"))
await args.Router.Navigate($"profile/{parameters.ProfileId}/workshop");
}
// Otherwise, show the profile editor if not already on the editor page
else if (!args.Path.EndsWith("editor"))
await args.Router.Navigate($"profile/{parameters.ProfileId}/editor");
}
}
public class ProfileViewModelParameters
{
public Guid ProfileId { get; set; }
}

View File

@ -0,0 +1,23 @@
<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:profileEditor="clr-namespace:Artemis.UI.Screens.ProfileEditor"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.WorkshopProfileView"
x:DataType="profileEditor:WorkshopProfileViewModel">
<Border Classes="router-container">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Vertical" Spacing="10" MaxWidth="800">
<ContentControl Content="{CompiledBinding EntryViewModel}"></ContentControl>
<TextBlock TextWrapping="Wrap">
The profile you're opening is a workshop profile.
</TextBlock>
<TextBlock TextWrapping="Wrap">
You cannot make change to it without disabling auto-update, as any updates to the profile on the workshop would override your own modifications.
</TextBlock>
<Button Command="{CompiledBinding DisableAutoUpdate}" Classes="accent">Disable auto-update</Button>
</StackPanel>
</Border>
</UserControl>

View File

@ -0,0 +1,11 @@
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor;
public partial class WorkshopProfileView : ReactiveUserControl<WorkshopProfileViewModel>
{
public WorkshopProfileView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,65 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Screens.Workshop.Library.Tabs;
using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Workshop.Models;
using Artemis.WebClient.Workshop.Services;
using PropertyChanged.SourceGenerator;
namespace Artemis.UI.Screens.ProfileEditor;
public partial class WorkshopProfileViewModel : RoutableScreen<ProfileViewModelParameters>
{
private readonly IProfileService _profileService;
private readonly IWorkshopService _workshopService;
private readonly IRouter _router;
private readonly Func<InstalledEntry, InstalledTabItemViewModel> _getInstalledTabItemViewModel;
[Notify] private ProfileConfiguration? _profileConfiguration;
[Notify] private InstalledEntry? _workshopEntry;
[Notify] private InstalledTabItemViewModel? _entryViewModel;
public WorkshopProfileViewModel(IProfileService profileService, IWorkshopService workshopService, IRouter router, Func<InstalledEntry, InstalledTabItemViewModel> getInstalledTabItemViewModel)
{
_profileService = profileService;
_workshopService = workshopService;
_router = router;
_getInstalledTabItemViewModel = getInstalledTabItemViewModel;
ParameterSource = ParameterSource.Route;
}
public async Task DisableAutoUpdate()
{
if (WorkshopEntry != null)
{
_workshopService.SetAutoUpdate(WorkshopEntry, false);
}
if (ProfileConfiguration != null)
{
await _router.Navigate($"profile/{ProfileConfiguration.ProfileId}/editor");
}
}
public override Task OnNavigating(ProfileViewModelParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
{
ProfileConfiguration = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == parameters.ProfileId);
// If the profile doesn't exist, cancel navigation
if (ProfileConfiguration == null)
{
args.Cancel();
return Task.CompletedTask;
}
WorkshopEntry = _workshopService.GetInstalledEntryByProfile(ProfileConfiguration);
EntryViewModel = WorkshopEntry != null ? _getInstalledTabItemViewModel(WorkshopEntry) : null;
if (EntryViewModel != null)
EntryViewModel.DisplayManagement = false;
return Task.CompletedTask;
}
}

View File

@ -65,7 +65,7 @@ public partial class SidebarCategoryViewModel : ActivatableViewModelBase
// Navigate on selection change // Navigate on selection change
this.WhenAnyValue(vm => vm.SelectedProfileConfiguration) this.WhenAnyValue(vm => vm.SelectedProfileConfiguration)
.WhereNotNull() .WhereNotNull()
.Subscribe(s => _router.Navigate($"profile-editor/{s.ProfileConfiguration.ProfileId}", new RouterNavigationOptions {IgnoreOnPartialMatch = true, RecycleScreens = false})) .Subscribe(s => _router.Navigate($"profile/{s.ProfileConfiguration.ProfileId}/editor", new RouterNavigationOptions {IgnoreOnPartialMatch = true, RecycleScreens = false}))
.DisposeWith(d); .DisposeWith(d);
_router.CurrentPath.WhereNotNull().Subscribe(r => SelectedProfileConfiguration = ProfileConfigurations.FirstOrDefault(c => c.Matches(r))).DisposeWith(d); _router.CurrentPath.WhereNotNull().Subscribe(r => SelectedProfileConfiguration = ProfileConfigurations.FirstOrDefault(c => c.Matches(r))).DisposeWith(d);

View File

@ -132,6 +132,6 @@ public class SidebarProfileConfigurationViewModel : ActivatableViewModelBase
public bool Matches(string s) public bool Matches(string s)
{ {
return s.StartsWith("profile-editor") && s.EndsWith(ProfileConfiguration.ProfileId.ToString()); return s == $"profile/{ProfileConfiguration.ProfileId}/editor";
} }
} }

View File

@ -71,6 +71,9 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
else if (result.Entry?.EntryType == EntryType.Plugin) else if (result.Entry?.EntryType == EntryType.Plugin)
{ {
await EnablePluginAndFeatures(result.Entry); await EnablePluginAndFeatures(result.Entry);
} else if (result.Entry?.EntryType == EntryType.Profile)
{
} }
return result.IsSuccess; return result.IsSuccess;

View File

@ -92,7 +92,7 @@
</StackPanel> </StackPanel>
<!-- Management --> <!-- Management -->
<Border Grid.Column="3" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0"> <Border Grid.Column="3" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0" IsVisible="{CompiledBinding DisplayManagement}">
<StackPanel VerticalAlignment="Center"> <StackPanel VerticalAlignment="Center">
<StackPanel Orientation="Horizontal" Spacing="5"> <StackPanel Orientation="Horizontal" Spacing="5">
<Button Command="{CompiledBinding ViewLocal}" HorizontalAlignment="Stretch" >Open</Button> <Button Command="{CompiledBinding ViewLocal}" HorizontalAlignment="Stretch" >Open</Button>

View File

@ -35,6 +35,7 @@ public partial class InstalledTabItemViewModel : ActivatableViewModelBase
[Notify] private bool _updateAvailable; [Notify] private bool _updateAvailable;
[Notify] private bool _autoUpdate; [Notify] private bool _autoUpdate;
[Notify] private bool _displayManagement = true;
public InstalledTabItemViewModel(InstalledEntry entry, public InstalledTabItemViewModel(InstalledEntry entry,
IWorkshopClient client, IWorkshopClient client,
@ -87,7 +88,7 @@ public partial class InstalledTabItemViewModel : ActivatableViewModelBase
public async Task ViewLocal() public async Task ViewLocal()
{ {
if (Entry.EntryType == EntryType.Profile && Entry.TryGetMetadata("ProfileId", out Guid profileId)) if (Entry.EntryType == EntryType.Profile && Entry.TryGetMetadata("ProfileId", out Guid profileId))
await _router.Navigate($"profile-editor/{profileId}"); await _router.Navigate($"profile/{profileId}/editor");
else if (Entry.EntryType == EntryType.Plugin) else if (Entry.EntryType == EntryType.Plugin)
await _router.Navigate($"workshop/entries/plugins/details/{Entry.Id}/manage"); await _router.Navigate($"workshop/entries/plugins/details/{Entry.Id}/manage");
else if (Entry.EntryType == EntryType.Layout) else if (Entry.EntryType == EntryType.Layout)