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

Workshop - Added library view models

This commit is contained in:
Robert 2023-08-31 22:04:57 +02:00
parent c75e839756
commit e545d2f3da
18 changed files with 272 additions and 41 deletions

View File

@ -0,0 +1,26 @@
using System;
namespace Artemis.UI.Routing;
public class RouteViewModel
{
public RouteViewModel(string path, string name)
{
Path = path;
Name = name;
}
public string Path { get; }
public string Name { get; }
public bool Matches(string path)
{
return path.StartsWith(Path, StringComparison.InvariantCultureIgnoreCase);
}
/// <inheritdoc />
public override string ToString()
{
return Name;
}
}

View File

@ -8,6 +8,8 @@ using Artemis.UI.Screens.SurfaceEditor;
using Artemis.UI.Screens.Workshop;
using Artemis.UI.Screens.Workshop.Home;
using Artemis.UI.Screens.Workshop.Layout;
using Artemis.UI.Screens.Workshop.Library;
using Artemis.UI.Screens.Workshop.Library.Tabs;
using Artemis.UI.Screens.Workshop.Profile;
using Artemis.UI.Shared.Routing;
@ -28,7 +30,12 @@ public static class Routes
new RouteRegistration<ProfileListViewModel>("profiles/{page:int}"),
new RouteRegistration<ProfileDetailsViewModel>("profiles/{entryId:guid}"),
new RouteRegistration<LayoutListViewModel>("layouts/{page:int}"),
new RouteRegistration<LayoutDetailsViewModel>("layouts/{entryId:guid}")
new RouteRegistration<LayoutDetailsViewModel>("layouts/{entryId:guid}"),
new RouteRegistration<WorkshopLibraryViewModel>("library") {Children = new List<IRouterRegistration>()
{
new RouteRegistration<LibraryInstalledViewModel>("installed"),
new RouteRegistration<LibrarySubmissionsViewModel>("submissions"),
}}
}
},
#endif

View File

@ -1,20 +0,0 @@
using System;
namespace Artemis.UI.Screens.Settings;
public class SettingsTab
{
public SettingsTab(string path, string name)
{
Path = path;
Name = name;
}
public string Path { get; set; }
public string Name { get; set; }
public bool Matches(string path)
{
return path.StartsWith($"settings/{Path}", StringComparison.InvariantCultureIgnoreCase);
}
}

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks;
using Artemis.UI.Routing;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using ReactiveUI;
@ -13,30 +14,30 @@ namespace Artemis.UI.Screens.Settings;
public class SettingsViewModel : RoutableScreen<ActivatableViewModelBase>, IMainScreenViewModel
{
private readonly IRouter _router;
private SettingsTab? _selectedTab;
private RouteViewModel? _selectedTab;
public SettingsViewModel(IRouter router)
{
_router = router;
SettingTabs = new ObservableCollection<SettingsTab>
SettingTabs = new ObservableCollection<RouteViewModel>
{
new("general", "General"),
new("plugins", "Plugins"),
new("devices", "Devices"),
new("releases", "Releases"),
new("about", "About"),
new("settings/general", "General"),
new("settings/plugins", "Plugins"),
new("settings/devices", "Devices"),
new("settings/releases", "Releases"),
new("settings/about", "About"),
};
// Navigate on tab change
this.WhenActivated(d => this.WhenAnyValue(vm => vm.SelectedTab)
.WhereNotNull()
.Subscribe(s => _router.Navigate($"settings/{s.Path}", new RouterNavigationOptions {IgnoreOnPartialMatch = true}))
.Subscribe(s => _router.Navigate(s.Path, new RouterNavigationOptions {IgnoreOnPartialMatch = true}))
.DisposeWith(d));
}
public ObservableCollection<SettingsTab> SettingTabs { get; }
public ObservableCollection<RouteViewModel> SettingTabs { get; }
public SettingsTab? SelectedTab
public RouteViewModel? SelectedTab
{
get => _selectedTab;
set => RaiseAndSetIfChanged(ref _selectedTab, value);
@ -52,6 +53,6 @@ public class SettingsViewModel : RoutableScreen<ActivatableViewModelBase>, IMain
// Always show a tab, if there is none forward to the first
if (SelectedTab == null)
await _router.Navigate($"settings/{SettingTabs.First().Path}");
await _router.Navigate(SettingTabs.First().Path);
}
}

View File

@ -0,0 +1,8 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Entries.EntryInstallationDialogView">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,13 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Screens.Workshop.Entries;
public partial class EntryInstallationDialogView : UserControl
{
public EntryInstallationDialogView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,8 @@
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.Workshop.Entries;
public class EntryInstallationDialogViewModel : ContentDialogViewModelBase
{
}

View File

@ -41,14 +41,6 @@
<StackPanel Margin="30 -75 30 0" Grid.Row="1">
<StackPanel Spacing="10" Orientation="Horizontal" VerticalAlignment="Top">
<Button Width="150" Height="180" Command="{CompiledBinding AddSubmission}" VerticalContentAlignment="Top">
<StackPanel>
<avalonia:MaterialIcon Kind="CloudUpload" HorizontalAlignment="Left" Width="60" Height="60" Margin="0 5" />
<TextBlock TextWrapping="Wrap" FontSize="16" Margin="0 5">Add submission</TextBlock>
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.8">Upload your own creations to the workshop!</TextBlock>
</StackPanel>
</Button>
<Button Width="150" Height="180" Command="{CompiledBinding Navigate}" CommandParameter="workshop/profiles/1" VerticalContentAlignment="Top">
<StackPanel>
<avalonia:MaterialIcon Kind="FolderVideo" HorizontalAlignment="Left" Width="60" Height="60" Margin="0 5" />
@ -64,6 +56,22 @@
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.8">Layouts make your devices look great in the editor.</TextBlock>
</StackPanel>
</Button>
<Button Width="150" Height="180" Command="{CompiledBinding Navigate}" CommandParameter="workshop/library" VerticalContentAlignment="Top">
<StackPanel>
<avalonia:MaterialIcon Kind="Bookshelf" HorizontalAlignment="Left" Width="60" Height="60" Margin="0 5" />
<TextBlock TextWrapping="Wrap" FontSize="16" Margin="0 5">Library</TextBlock>
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.8">Manage your submissions and downloaded content.</TextBlock>
</StackPanel>
</Button>
<Button Width="150" Height="180" Command="{CompiledBinding AddSubmission}" VerticalContentAlignment="Top">
<StackPanel>
<avalonia:MaterialIcon Kind="CloudUpload" HorizontalAlignment="Left" Width="60" Height="60" Margin="0 5" />
<TextBlock TextWrapping="Wrap" FontSize="16" Margin="0 5">Add submission</TextBlock>
<TextBlock TextWrapping="Wrap" FontSize="12" Opacity="0.8">Upload your own creations to the workshop!</TextBlock>
</StackPanel>
</Button>
</StackPanel>

View File

@ -0,0 +1,8 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Library.Tabs.LibraryInstalledView">
Installed entries management here 🫡
</UserControl>

View File

@ -0,0 +1,14 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
public partial class LibraryInstalledView : ReactiveUserControl<LibraryInstalledViewModel>
{
public LibraryInstalledView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,8 @@
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
public class LibraryInstalledViewModel : ActivatableViewModelBase
{
}

View File

@ -0,0 +1,8 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Library.Tabs.LibrarySubmissionsView">
Submission management here 😗
</UserControl>

View File

@ -0,0 +1,14 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
public partial class LibrarySubmissionsView : ReactiveUserControl<LibrarySubmissionsViewModel>
{
public LibrarySubmissionsView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,8 @@
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
public class LibrarySubmissionsViewModel : ActivatableViewModelBase
{
}

View File

@ -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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:library="clr-namespace:Artemis.UI.Screens.Workshop.Library"
xmlns:routing="clr-namespace:Artemis.UI.Routing"
xmlns:ui1="clr-namespace:Artemis.UI"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Library.WorkshopLibraryView"
x:DataType="library:WorkshopLibraryViewModel">
<ui:NavigationView PaneDisplayMode="Top" MenuItemsSource="{CompiledBinding Tabs}" SelectedItem="{CompiledBinding SelectedTab}">
<ui:NavigationView.Styles>
<Styles>
<Style Selector="ui|NavigationView:topnavminimal /template/ SplitView Border#ContentGridBorder">
<Setter Property="CornerRadius" Value="8 0 0 0" />
</Style>
</Styles>
</ui:NavigationView.Styles>
<ui:Frame Name="TabFrame" IsNavigationStackEnabled="False" CacheSize="0">
<ui:Frame.NavigationPageFactory>
<ui1:PageFactory/>
</ui:Frame.NavigationPageFactory>
</ui:Frame>
</ui:NavigationView>
</UserControl>

View File

@ -0,0 +1,37 @@
using System;
using System.Reactive.Disposables;
using Artemis.UI.Shared;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using FluentAvalonia.UI.Media.Animation;
using FluentAvalonia.UI.Navigation;
using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Library;
public partial class WorkshopLibraryView : ReactiveUserControl<WorkshopLibraryViewModel>
{
private int _lastIndex;
public WorkshopLibraryView()
{
InitializeComponent();
this.WhenActivated(d => { ViewModel.WhenAnyValue(vm => vm.Screen).WhereNotNull().Subscribe(Navigate).DisposeWith(d); });
}
private void Navigate(ViewModelBase viewModel)
{
Dispatcher.UIThread.Invoke(() =>
{
if (ViewModel == null)
return;
SlideNavigationTransitionInfo transitionInfo = new()
{
Effect = ViewModel.Tabs.IndexOf(ViewModel.SelectedTab) > _lastIndex ? SlideNavigationTransitionEffect.FromRight : SlideNavigationTransitionEffect.FromLeft
};
TabFrame.NavigateFromObject(viewModel, new FrameNavigationOptions {TransitionInfoOverride = transitionInfo});
_lastIndex = ViewModel.Tabs.IndexOf(ViewModel.SelectedTab);
});
}
}

View File

@ -0,0 +1,50 @@
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;
using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Workshop;
using ReactiveUI;
using System;
namespace Artemis.UI.Screens.Workshop.Library;
public class WorkshopLibraryViewModel : RoutableScreen<ActivatableViewModelBase>, IWorkshopViewModel
{
private RouteViewModel? _selectedTab;
/// <inheritdoc />
public WorkshopLibraryViewModel(IRouter router)
{
Tabs = new ObservableCollection<RouteViewModel>
{
new("workshop/library/installed", "Installed"),
new("workshop/library/submissions", "Submissions")
};
// Navigate on tab change
this.WhenActivated(d => this.WhenAnyValue(vm => vm.SelectedTab)
.WhereNotNull()
.Subscribe(s => router.Navigate(s.Path, new RouterNavigationOptions {IgnoreOnPartialMatch = true}))
.DisposeWith(d));
}
public EntryType? EntryType => null;
public ObservableCollection<RouteViewModel> 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);
}
}

View File

@ -23,14 +23,16 @@ public class ProfileDetailsViewModel : RoutableScreen<ActivatableViewModelBase,
private readonly IWorkshopClient _client;
private readonly ProfileEntryDownloadHandler _downloadHandler;
private readonly INotificationService _notificationService;
private readonly IWindowService _windowService;
private readonly ObservableAsPropertyHelper<DateTimeOffset?> _updatedAt;
private IGetEntryById_Entry? _entry;
public ProfileDetailsViewModel(IWorkshopClient client, ProfileEntryDownloadHandler downloadHandler, INotificationService notificationService)
public ProfileDetailsViewModel(IWorkshopClient client, ProfileEntryDownloadHandler downloadHandler, INotificationService notificationService, IWindowService windowService)
{
_client = client;
_downloadHandler = downloadHandler;
_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);
@ -65,6 +67,10 @@ public class ProfileDetailsViewModel : RoutableScreen<ActivatableViewModelBase,
if (Entry?.LatestRelease == null)
return;
bool confirm = await _windowService.ShowConfirmContentDialog("Install profile?", "The profile will be downloaded and added to your sidebar automatically.");
if (!confirm)
return;
EntryInstallResult<ProfileConfiguration> result = await _downloadHandler.InstallProfileAsync(Entry.LatestRelease.Id, new Progress<StreamProgress>(), cancellationToken);
if (result.IsSuccess)
_notificationService.CreateNotification().WithTitle("Profile installed").WithSeverity(NotificationSeverity.Success).Show();