1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-02-04 02:43:32 +00:00

Surface editor - Show model instead of device name

Settings - Add link to plugin's workshop page
Workshop library - Recently updated tab (WIP)
This commit is contained in:
Robert 2025-12-29 12:30:24 +01:00
parent 0823de45a4
commit 74d5480d7b
11 changed files with 139 additions and 80 deletions

View File

@ -50,7 +50,8 @@ namespace Artemis.UI.Routing
new RouteRegistration<SubmissionsTabViewModel>("submissions"),
new RouteRegistration<SubmissionManagementViewModel>("submissions/{entryId:long}", [
new RouteRegistration<SubmissionReleaseViewModel>("releases/{releaseId:long}")
])
]),
new RouteRegistration<RecentlyUpdatedViewModel>("recently-updated")
])
]),
new RouteRegistration<SurfaceEditorViewModel>("surface-editor"),

View File

@ -92,6 +92,12 @@
ToolTip.Tip="Open settings">
<avalonia:MaterialIcon Kind="Cog" />
</HyperlinkButton>
<HyperlinkButton Classes="icon-button icon-button-large"
IsVisible="{CompiledBinding WorkshopEntry, Converter={x:Static ObjectConverters.IsNotNull}}"
Command="{CompiledBinding ViewEntry}"
ToolTip.Tip="View on workshop">
<avalonia:MaterialIcon Kind="TestTube" />
</HyperlinkButton>
<HyperlinkButton Classes="icon-button icon-button-large"
IsVisible="{CompiledBinding PluginInfo.HelpPage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
NavigateUri="{CompiledBinding PluginInfo.HelpPage}"

View File

@ -11,6 +11,8 @@ using Artemis.UI.Exceptions;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop.Models;
using Artemis.WebClient.Workshop.Services;
using Avalonia.Controls;
using Avalonia.Threading;
using Material.Icons;
@ -22,21 +24,25 @@ namespace Artemis.UI.Screens.Plugins;
public partial class PluginViewModel : ActivatableViewModelBase
{
private readonly IPluginInteractionService _pluginInteractionService;
private readonly IWorkshopService _workshopService;
private readonly IWindowService _windowService;
private Window? _settingsWindow;
[Notify] private bool _canInstallPrerequisites;
[Notify] private bool _canRemovePrerequisites;
[Notify] private bool _enabling;
[Notify] private InstalledEntry? _workshopEntry;
public PluginInfo PluginInfo { get; }
public Plugin? Plugin { get; }
public PluginViewModel(PluginInfo pluginInfo, ReactiveCommand<Unit, Unit>? reload, IWindowService windowService, IPluginInteractionService pluginInteractionService)
public PluginViewModel(PluginInfo pluginInfo, ReactiveCommand<Unit, Unit>? reload, IWindowService windowService, IPluginInteractionService pluginInteractionService,
IWorkshopService workshopService)
{
PluginInfo = pluginInfo;
Plugin = pluginInfo?.Plugin;
_windowService = windowService;
_pluginInteractionService = pluginInteractionService;
_workshopService = workshopService;
Platforms = [];
if (PluginInfo.Platforms != null)
@ -51,12 +57,8 @@ public partial class PluginViewModel : ActivatableViewModelBase
Reload = reload;
OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(vm => vm.IsEnabled, e => e && Plugin?.ConfigurationDialog != null));
RemoveSettings = ReactiveCommand.CreateFromTask(ExecuteRemoveSettings);
Remove = ReactiveCommand.CreateFromTask(ExecuteRemove);
InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites));
RemovePrerequisites = ReactiveCommand.CreateFromTask<bool>(ExecuteRemovePrerequisites, this.WhenAnyValue(x => x.CanRemovePrerequisites));
ShowLogsFolder = ReactiveCommand.Create(ExecuteShowLogsFolder);
OpenPluginDirectory = ReactiveCommand.Create(ExecuteOpenPluginDirectory);
this.WhenActivated(d =>
{
@ -65,6 +67,7 @@ public partial class PluginViewModel : ActivatableViewModelBase
Plugin.Enabled += OnPluginToggled;
Plugin.Disabled += OnPluginToggled;
WorkshopEntry = _workshopService.GetInstalledEntryByPlugin(Plugin);
Disposable.Create(() =>
{
@ -77,12 +80,8 @@ public partial class PluginViewModel : ActivatableViewModelBase
public ReactiveCommand<Unit, Unit>? Reload { get; }
public ReactiveCommand<Unit, Unit> OpenSettings { get; }
public ReactiveCommand<Unit, Unit> RemoveSettings { get; }
public ReactiveCommand<Unit, Unit> Remove { get; }
public ReactiveCommand<Unit, Unit> InstallPrerequisites { get; }
public ReactiveCommand<bool, Unit> RemovePrerequisites { get; }
public ReactiveCommand<Unit, Unit> ShowLogsFolder { get; }
public ReactiveCommand<Unit, Unit> OpenPluginDirectory { get; }
public ObservableCollection<PluginPlatformViewModel> Platforms { get; }
public bool IsEnabled => Plugin != null && Plugin.IsEnabled;
@ -121,6 +120,68 @@ public partial class PluginViewModel : ActivatableViewModelBase
});
}
public void OpenPluginDirectory()
{
try
{
if (Plugin != null)
Utilities.OpenFolder(Plugin.Directory.FullName);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
}
}
public async Task RemoveSettings()
{
if (Plugin == null)
return;
await _pluginInteractionService.RemovePluginSettings(Plugin);
}
public async Task Remove()
{
if (Plugin == null)
return;
await _pluginInteractionService.RemovePlugin(Plugin);
}
public async Task AutoEnable()
{
if (IsEnabled)
return;
await UpdateEnabled(true);
// If enabling failed, don't offer to show the settings
if (!IsEnabled || Plugin?.ConfigurationDialog == null)
return;
if (await _windowService.ShowConfirmContentDialog("Open plugin settings", "This plugin has settings, would you like to view them?", "Yes", "No"))
ExecuteOpenSettings();
}
public async Task ViewEntry()
{
if (WorkshopEntry != null)
await _workshopService.NavigateToEntry(WorkshopEntry.Id, WorkshopEntry.EntryType);
}
public async Task ExecuteRemovePrerequisites(bool forPluginRemoval = false)
{
if (Plugin == null)
return;
List<IPrerequisitesSubject> subjects = [PluginInfo];
subjects.AddRange(!forPluginRemoval ? Plugin.Features.Where(f => f.AlwaysEnabled) : Plugin.Features);
if (subjects.Any(s => s.PlatformPrerequisites.Any(p => p.UninstallActions.Any())))
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, subjects, forPluginRemoval ? "Skip, remove plugin" : "Cancel");
}
private void ExecuteOpenSettings()
{
if (Plugin?.ConfigurationDialog == null)
@ -148,19 +209,6 @@ public partial class PluginViewModel : ActivatableViewModelBase
}
}
private void ExecuteOpenPluginDirectory()
{
try
{
if (Plugin != null)
Utilities.OpenFolder(Plugin.Directory.FullName);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
}
}
private async Task ExecuteInstallPrerequisites()
{
if (Plugin == null)
@ -173,46 +221,6 @@ public partial class PluginViewModel : ActivatableViewModelBase
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
}
public async Task ExecuteRemovePrerequisites(bool forPluginRemoval = false)
{
if (Plugin == null)
return;
List<IPrerequisitesSubject> subjects = [PluginInfo];
subjects.AddRange(!forPluginRemoval ? Plugin.Features.Where(f => f.AlwaysEnabled) : Plugin.Features);
if (subjects.Any(s => s.PlatformPrerequisites.Any(p => p.UninstallActions.Any())))
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, subjects, forPluginRemoval ? "Skip, remove plugin" : "Cancel");
}
private async Task ExecuteRemoveSettings()
{
if (Plugin == null)
return;
await _pluginInteractionService.RemovePluginSettings(Plugin);
}
private async Task ExecuteRemove()
{
if (Plugin == null)
return;
await _pluginInteractionService.RemovePlugin(Plugin);
}
private void ExecuteShowLogsFolder()
{
try
{
Utilities.OpenFolder(Constants.LogsFolder);
}
catch (Exception e)
{
_windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
}
}
private void OnPluginToggled(object? sender, EventArgs e)
{
Dispatcher.UIThread.Post(() =>
@ -222,19 +230,4 @@ public partial class PluginViewModel : ActivatableViewModelBase
_settingsWindow?.Close();
});
}
public async Task AutoEnable()
{
if (IsEnabled)
return;
await UpdateEnabled(true);
// If enabling failed, don't offer to show the settings
if (!IsEnabled || Plugin?.ConfigurationDialog == null)
return;
if (await _windowService.ShowConfirmContentDialog("Open plugin settings", "This plugin has settings, would you like to view them?", "Yes", "No"))
ExecuteOpenSettings();
}
}

View File

@ -11,7 +11,7 @@
<Border Grid.Column="0" Grid.RowSpan="2" Width="64" Height="50" Margin="0 0 10 0">
<shared:DeviceVisualizer Device="{CompiledBinding Device}" ShowColors="True" VerticalAlignment="Center" HorizontalAlignment="Center" RenderOptions.BitmapInterpolationMode="MediumQuality"/>
</Border>
<TextBlock Grid.Column="1" Grid.Row="0" Text="{CompiledBinding Device.RgbDevice.DeviceInfo.DeviceName}" VerticalAlignment="Bottom" />
<TextBlock Grid.Column="1" Grid.Row="0" Text="{CompiledBinding Device.RgbDevice.DeviceInfo.Model}" VerticalAlignment="Bottom" />
<TextBlock Grid.Column="1" Grid.Row="1" Classes="subtitle" Text="{CompiledBinding Device.RgbDevice.DeviceInfo.Manufacturer}" VerticalAlignment="Top" />
</Grid>
</UserControl>

View File

@ -0,0 +1,10 @@
<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:tabs="clr-namespace:Artemis.UI.Screens.Workshop.Library.Tabs"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Library.Tabs.RecentlyUpdatedItemView"
x:DataType="tabs:RecentlyUpdatedItemViewModel">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,11 @@
using ReactiveUI.Avalonia;
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
public partial class RecentlyUpdatedItemView : ReactiveUserControl<RecentlyUpdatedItemViewModel>
{
public RecentlyUpdatedItemView()
{
InitializeComponent();
}
}

View File

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

View File

@ -0,0 +1,10 @@
<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:tabs="clr-namespace:Artemis.UI.Screens.Workshop.Library.Tabs"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.Library.Tabs.RecentlyUpdatedView"
x:DataType="tabs:RecentlyUpdatedViewModel">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,11 @@
using ReactiveUI.Avalonia;
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
public partial class RecentlyUpdatedView : ReactiveUserControl<InstalledTabViewModel>
{
public RecentlyUpdatedView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,8 @@
using Artemis.UI.Shared.Routing;
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
public partial class RecentlyUpdatedViewModel : RoutableScreen
{
}

View File

@ -26,7 +26,8 @@ public partial class WorkshopLibraryViewModel : RoutableHostScreen<RoutableScree
Tabs =
[
new RouteViewModel("Installed", "workshop/library/installed"),
new RouteViewModel("Submissions", "workshop/library/submissions")
new RouteViewModel("Submissions", "workshop/library/submissions"),
new RouteViewModel("Recently Updated", "workshop/library/recently-updated")
];
this.WhenActivated(d =>