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

Workshop - Improves default entry handling in workshop

Core - Modifies the Artemis data folder path based on the build configuration.
Plugins - Adds a confirmation dialog to prompt the user to restart if a plugin requires admin rights.
Startup wizard - Updates the installation progress bar in the startup wizard.
Workshop - Changes category retrieval to be based on EntryType.
Workshop - Adds the ability to include default entries when searching the workshop.
This commit is contained in:
Robert 2025-12-08 22:27:18 +01:00
parent f0cbc3e561
commit 565647f1ed
19 changed files with 55 additions and 43 deletions

View File

@ -40,7 +40,11 @@ public static class Constants
/// <summary> /// <summary>
/// The full path to the Artemis data folder /// The full path to the Artemis data folder
/// </summary> /// </summary>
#if DEBUG
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis-dev"); public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis-dev");
#else
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis");
#endif
/// <summary> /// <summary>
/// The full path to the Artemis logs folder /// The full path to the Artemis logs folder

View File

@ -119,7 +119,7 @@ public partial class PluginViewModel : ActivatableViewModelBase
Enabling = true; Enabling = true;
if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated) if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
{ {
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?"); bool confirmed = await _windowService.ShowConfirmContentDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it? Artemis will need to restart.", "Confirm and restart");
if (!confirmed) if (!confirmed)
return; return;
} }

View File

@ -62,7 +62,6 @@
<ProgressBar Minimum="0" <ProgressBar Minimum="0"
Maximum="{CompiledBinding TotalEntries}" Maximum="{CompiledBinding TotalEntries}"
Value="{CompiledBinding CurrentEntry.InstallProgress, FallbackValue=0}" Value="{CompiledBinding CurrentEntry.InstallProgress, FallbackValue=0}"
IsIndeterminate="{CompiledBinding CurrentEntry.Enabling, FallbackValue=true}"
Margin="0 0 0 5" /> Margin="0 0 0 5" />
<TextBlock> <TextBlock>
<Run>Currently installing: </Run> <Run>Currently installing: </Run>

View File

@ -41,7 +41,7 @@ public partial class DefaultEntriesStepViewModel : WizardStepViewModel
_client = client; _client = client;
_getDefaultEntryItemViewModel = getDefaultEntryItemViewModel; _getDefaultEntryItemViewModel = getDefaultEntryItemViewModel;
_remainingEntries = this.WhenAnyValue(vm => vm.InstalledEntries, vm => vm.TotalEntries) _remainingEntries = this.WhenAnyValue(vm => vm.InstalledEntries, vm => vm.TotalEntries)
.Select(t => t.Item2 - t.Item1) .Select(t => t.Item2 - t.Item1 + 1)
.ToProperty(this, vm => vm.RemainingEntries); .ToProperty(this, vm => vm.RemainingEntries);
ContinueText = "Install selected entries"; ContinueText = "Install selected entries";

View File

@ -31,7 +31,6 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
[Notify] private bool _isInstalled; [Notify] private bool _isInstalled;
[Notify] private bool _shouldInstall; [Notify] private bool _shouldInstall;
[Notify] private bool _enabling;
[Notify] private float _installProgress; [Notify] private float _installProgress;
public DefaultEntryItemViewModel(ILogger logger, IEntrySummary entry, IWorkshopService workshopService, IWindowService windowService, IPluginManagementService pluginManagementService, public DefaultEntryItemViewModel(ILogger logger, IEntrySummary entry, IWorkshopService workshopService, IWindowService windowService, IPluginManagementService pluginManagementService,
@ -70,9 +69,7 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
// If the entry is a plugin, enable the plugin and all features // If the entry is a plugin, enable the plugin and all features
else if (result.Entry?.EntryType == EntryType.Plugin) else if (result.Entry?.EntryType == EntryType.Plugin)
{ {
Enabling = true;
await EnablePluginAndFeatures(result.Entry); await EnablePluginAndFeatures(result.Entry);
Enabling = false;
} }
return result.IsSuccess; return result.IsSuccess;

View File

@ -18,10 +18,10 @@ public class CategoriesViewModel : ActivatableViewModelBase
{ {
private ObservableAsPropertyHelper<IReadOnlyList<EntryFilterInput>?>? _categoryFilters; private ObservableAsPropertyHelper<IReadOnlyList<EntryFilterInput>?>? _categoryFilters;
public CategoriesViewModel(IWorkshopClient client) public CategoriesViewModel(EntryType entryType, IWorkshopClient client)
{ {
client.GetCategories client.GetCategories
.Watch(ExecutionStrategy.CacheFirst) .Watch(entryType, ExecutionStrategy.CacheFirst)
.SelectOperationResult(c => c.Categories) .SelectOperationResult(c => c.Categories)
.ToObservableChangeSet(c => c.Id) .ToObservableChangeSet(c => c.Id)
.Transform(c => new CategoryViewModel(c)) .Transform(c => new CategoryViewModel(c))

View File

@ -88,6 +88,7 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
public bool IsAdministrator { get; } public bool IsAdministrator { get; }
public List<long> PreselectedCategories { get; set; } = new(); public List<long> PreselectedCategories { get; set; } = new();
public EntryType EntryType { get; set; }
private async Task ExecuteSelectIcon() private async Task ExecuteSelectIcon()
{ {
@ -114,7 +115,7 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
private async Task PopulateCategories() private async Task PopulateCategories()
{ {
IOperationResult<IGetCategoriesResult> categories = await _workshopClient.GetCategories.ExecuteAsync(); IOperationResult<IGetCategoriesResult> categories = await _workshopClient.GetCategories.ExecuteAsync(EntryType);
Categories.Clear(); Categories.Clear();
if (categories.Data != null) if (categories.Data != null)
Categories.AddRange(categories.Data.Categories.Select(c => new CategoryViewModel(c) {IsSelected = PreselectedCategories.Contains(c.Id)})); Categories.AddRange(categories.Data.Categories.Select(c => new CategoryViewModel(c) {IsSelected = PreselectedCategories.Contains(c.Id)}));

View File

@ -22,6 +22,7 @@
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Grid.RowSpan="3" <StackPanel Grid.Column="0" Grid.RowSpan="3"
Spacing="10"
Margin="0 0 10 0" Margin="0 0 10 0"
VerticalAlignment="Top" VerticalAlignment="Top"
HorizontalAlignment="Right" HorizontalAlignment="Right"
@ -32,20 +33,16 @@
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Categories</TextBlock> <TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Categories</TextBlock>
<Border Classes="card-separator" /> <Border Classes="card-separator" />
<ContentControl Content="{CompiledBinding CategoriesViewModel}"></ContentControl> <ContentControl Content="{CompiledBinding CategoriesViewModel}"></ContentControl>
<CheckBox IsChecked="{CompiledBinding IncludeDefaultEntries}">
<StackPanel Orientation="Horizontal" Spacing="2">
<Image Source="/Assets/Images/Logo/bow.png" RenderOptions.BitmapInterpolationMode="HighQuality" Width="18" Height="18"/>
<TextBlock>Default entries</TextBlock>
</StackPanel>
</CheckBox>
</StackPanel> </StackPanel>
</Border> </Border>
<Border Classes="card" VerticalAlignment="Stretch"> <Border Classes="card" VerticalAlignment="Stretch">
<StackPanel> <StackPanel>
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Categories</TextBlock> <CheckBox IsChecked="{CompiledBinding IncludeDefaultEntries}">
<Border Classes="card-separator" /> <StackPanel Orientation="Horizontal" Spacing="2">
<ContentControl Content="{CompiledBinding CategoriesViewModel}"></ContentControl> <TextBlock>Include default entries</TextBlock>
</StackPanel>
</CheckBox>
</StackPanel> </StackPanel>
</Border> </Border>
</StackPanel> </StackPanel>

View File

@ -25,7 +25,7 @@ public partial class EntryListViewModel : RoutableScreen
private readonly SourceList<IEntrySummary> _entries = new(); private readonly SourceList<IEntrySummary> _entries = new();
private readonly INotificationService _notificationService; private readonly INotificationService _notificationService;
private readonly IWorkshopClient _workshopClient; private readonly IWorkshopClient _workshopClient;
private IGetEntriesv2_EntriesV2_PageInfo? _currentPageInfo; private IGetEntries_EntriesV2_PageInfo? _currentPageInfo;
[Notify] private bool _initializing = true; [Notify] private bool _initializing = true;
[Notify] private bool _fetchingMore; [Notify] private bool _fetchingMore;
@ -33,18 +33,20 @@ public partial class EntryListViewModel : RoutableScreen
[Notify] private bool _includeDefaultEntries; [Notify] private bool _includeDefaultEntries;
[Notify] private Vector _scrollOffset; [Notify] private Vector _scrollOffset;
protected EntryListViewModel(IWorkshopClient workshopClient, protected EntryListViewModel(EntryType entryType,
CategoriesViewModel categoriesViewModel, IWorkshopClient workshopClient,
EntryListInputViewModel entryListInputViewModel, EntryListInputViewModel entryListInputViewModel,
INotificationService notificationService, INotificationService notificationService,
Func<EntryType, CategoriesViewModel> getCategoriesViewModel,
Func<IEntrySummary, EntryListItemViewModel> getEntryListViewModel) Func<IEntrySummary, EntryListItemViewModel> getEntryListViewModel)
{ {
_workshopClient = workshopClient; _workshopClient = workshopClient;
_notificationService = notificationService; _notificationService = notificationService;
CategoriesViewModel = categoriesViewModel; CategoriesViewModel = getCategoriesViewModel(entryType);
InputViewModel = entryListInputViewModel; InputViewModel = entryListInputViewModel;
EntryType = entryType;
_entries.Connect() _entries.Connect()
.Transform(getEntryListViewModel) .Transform(getEntryListViewModel)
.Bind(out ReadOnlyObservableCollection<EntryListItemViewModel> entries) .Bind(out ReadOnlyObservableCollection<EntryListItemViewModel> entries)
@ -75,7 +77,7 @@ public partial class EntryListViewModel : RoutableScreen
public CategoriesViewModel CategoriesViewModel { get; } public CategoriesViewModel CategoriesViewModel { get; }
public EntryListInputViewModel InputViewModel { get; } public EntryListInputViewModel InputViewModel { get; }
public bool ShowCategoryFilter { get; set; } = true; public bool ShowCategoryFilter { get; set; } = true;
public EntryType? EntryType { get; set; } public EntryType EntryType { get; }
public ReadOnlyObservableCollection<EntryListItemViewModel> Entries { get; } public ReadOnlyObservableCollection<EntryListItemViewModel> Entries { get; }
@ -93,7 +95,7 @@ public partial class EntryListViewModel : RoutableScreen
try try
{ {
IOperationResult<IGetEntriesv2Result> entries = await _workshopClient.GetEntriesv2.ExecuteAsync(search, filter, sort, entriesPerFetch, _currentPageInfo?.EndCursor, cancellationToken); IOperationResult<IGetEntriesResult> entries = await _workshopClient.GetEntries.ExecuteAsync(search, IncludeDefaultEntries, filter, sort, entriesPerFetch, _currentPageInfo?.EndCursor, cancellationToken);
entries.EnsureNoErrors(); entries.EnsureNoErrors();
_currentPageInfo = entries.Data?.EntriesV2?.PageInfo; _currentPageInfo = entries.Data?.EntriesV2?.PageInfo;
@ -124,7 +126,6 @@ public partial class EntryListViewModel : RoutableScreen
And = And =
[ [
new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = EntryType}}, new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = EntryType}},
new EntryFilterInput {DefaultEntryInfo = IncludeDefaultEntries ? new DefaultEntryInfoFilterInput {EntryId = new LongOperationFilterInput {Ngt = 0}} : null},
..CategoriesViewModel.CategoryFilters ?? [] ..CategoriesViewModel.CategoryFilters ?? []
] ]
}; };

View File

@ -51,7 +51,7 @@ public partial class WorkshopHomeViewModel : RoutableScreen
p.AddRange(popularResult.Data.PopularEntries.Take(8)); p.AddRange(popularResult.Data.PopularEntries.Take(8));
}); });
IOperationResult<IGetEntriesv2Result> latestResult = await client.GetEntriesv2.ExecuteAsync(null, null, [new EntrySortInput {CreatedAt = SortEnumType.Desc}], 8, null); IOperationResult<IGetEntriesResult> latestResult = await client.GetEntries.ExecuteAsync(null, null, null, [new EntrySortInput {CreatedAt = SortEnumType.Desc}], 8, null);
latest.Edit(l => latest.Edit(l =>
{ {
l.Clear(); l.Clear();

View File

@ -1,3 +1,4 @@
using System;
using Artemis.UI.Screens.Workshop.Entries.List; using Artemis.UI.Screens.Workshop.Entries.List;
using Artemis.UI.Screens.Workshop.LayoutFinder; using Artemis.UI.Screens.Workshop.LayoutFinder;
using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Routing;
@ -7,11 +8,10 @@ namespace Artemis.UI.Screens.Workshop.Layout;
public class LayoutListDefaultViewModel : RoutableScreen public class LayoutListDefaultViewModel : RoutableScreen
{ {
public LayoutListDefaultViewModel(LayoutFinderViewModel layoutFinderViewModel, EntryListViewModel entryListViewModel) public LayoutListDefaultViewModel(LayoutFinderViewModel layoutFinderViewModel, Func<EntryType, EntryListViewModel> getEntryListViewModel)
{ {
LayoutFinderViewModel = layoutFinderViewModel; LayoutFinderViewModel = layoutFinderViewModel;
EntryListViewModel = entryListViewModel; EntryListViewModel = getEntryListViewModel(EntryType.Layout);
EntryListViewModel.EntryType = EntryType.Layout;
EntryListViewModel.ShowCategoryFilter = false; EntryListViewModel.ShowCategoryFilter = false;
} }

View File

@ -109,6 +109,7 @@ public partial class SubmissionDetailsViewModel : RoutableScreen
specificationsViewModel.IsEssential = Entry.DefaultEntryInfo?.IsEssential ?? false; specificationsViewModel.IsEssential = Entry.DefaultEntryInfo?.IsEssential ?? false;
specificationsViewModel.IsDeviceProvider = Entry.DefaultEntryInfo?.IsDeviceProvider ?? false; specificationsViewModel.IsDeviceProvider = Entry.DefaultEntryInfo?.IsDeviceProvider ?? false;
specificationsViewModel.PreselectedCategories = Entry.Categories.Select(c => c.Id).ToList(); specificationsViewModel.PreselectedCategories = Entry.Categories.Select(c => c.Id).ToList();
specificationsViewModel.EntryType = Entry.EntryType;
specificationsViewModel.Tags.Clear(); specificationsViewModel.Tags.Clear();
foreach (string tag in Entry.Tags.Select(c => c.Name)) foreach (string tag in Entry.Tags.Select(c => c.Name))

View File

@ -1,3 +1,4 @@
using System;
using Artemis.UI.Screens.Workshop.Entries.List; using Artemis.UI.Screens.Workshop.Entries.List;
using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Workshop; using Artemis.WebClient.Workshop;
@ -9,9 +10,8 @@ public class PluginListViewModel : RoutableHostScreen<RoutableScreen>
private readonly EntryListViewModel _entryListViewModel; private readonly EntryListViewModel _entryListViewModel;
public override RoutableScreen DefaultScreen => _entryListViewModel; public override RoutableScreen DefaultScreen => _entryListViewModel;
public PluginListViewModel(EntryListViewModel entryListViewModel) public PluginListViewModel(Func<EntryType, EntryListViewModel> getEntryListViewModel)
{ {
_entryListViewModel = entryListViewModel; _entryListViewModel = getEntryListViewModel(EntryType.Plugin);
_entryListViewModel.EntryType = EntryType.Plugin;
} }
} }

View File

@ -1,3 +1,4 @@
using System;
using Artemis.UI.Screens.Workshop.Entries.List; using Artemis.UI.Screens.Workshop.Entries.List;
using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Workshop; using Artemis.WebClient.Workshop;
@ -9,9 +10,8 @@ public class ProfileListViewModel : RoutableHostScreen<RoutableScreen>
private readonly EntryListViewModel _entryListViewModel; private readonly EntryListViewModel _entryListViewModel;
public override RoutableScreen DefaultScreen => _entryListViewModel; public override RoutableScreen DefaultScreen => _entryListViewModel;
public ProfileListViewModel(EntryListViewModel entryListViewModel) public ProfileListViewModel(Func<EntryType, EntryListViewModel> getEntryListViewModel)
{ {
_entryListViewModel = entryListViewModel; _entryListViewModel = getEntryListViewModel(EntryType.Profile);
_entryListViewModel.EntryType = EntryType.Profile;
} }
} }

View File

@ -75,6 +75,7 @@ public partial class SpecificationsStepViewModel : SubmissionViewModel
// Categories // Categories
viewModel.PreselectedCategories = State.Categories; viewModel.PreselectedCategories = State.Categories;
viewModel.EntryType = State.EntryType;
// Icon // Icon
if (State.Icon != null) if (State.Icon != null)

View File

@ -38,7 +38,7 @@ public partial class SubmitStepViewModel : SubmissionViewModel
IconBitmap.DisposeWith(d); IconBitmap.DisposeWith(d);
} }
Observable.FromAsync(workshopClient.GetCategories.ExecuteAsync).Subscribe(PopulateCategories).DisposeWith(d); Observable.FromAsync(() => workshopClient.GetCategories.ExecuteAsync(State.EntryType)).Subscribe(PopulateCategories).DisposeWith(d);
}); });
} }

View File

@ -1,5 +1,5 @@
query GetCategories { query GetCategories($entryType: EntryType) {
categories(order: {name: ASC}) { categories(where: {entryType: {in: [$entryType, null]}} order: {name: ASC}) {
id id
name name
icon icon

View File

@ -1,5 +1,5 @@
query GetEntriesv2($search: String $filter: EntryFilterInput $order: [EntrySortInput!] $first: Int $after: String) { query GetEntries($search: String $includeDefaults: Boolean $filter: EntryFilterInput $order: [EntrySortInput!] $first: Int $after: String) {
entriesV2(search: $search where: $filter order: $order first: $first after: $after) { entriesV2(search: $search includeDefaults: $includeDefaults where: $filter order: $order first: $first after: $after) {
totalCount totalCount
pageInfo { pageInfo {
hasNextPage hasNextPage
@ -22,6 +22,7 @@ query GetPopularEntries {
query GetDefaultEntries($first: Int, $after: String) { query GetDefaultEntries($first: Int, $after: String) {
entriesV2( entriesV2(
includeDefaults: true
where: { defaultEntryInfo: { entryId: { gt: 0 } } } where: { defaultEntryInfo: { entryId: { gt: 0 } } }
first: $first first: $first
after: $after after: $after

View File

@ -7,6 +7,7 @@ type Category {
id: Long! id: Long!
name: String! name: String!
icon: String! icon: String!
entryType: EntryType
} }
"Information about the offset pagination." "Information about the offset pagination."
@ -166,7 +167,7 @@ type Query {
skip: Int skip: Int
take: Int take: Int
search: String search: String
popular: Boolean includeDefaults: Boolean
order: [EntrySortInput!] @cost(weight: "10") order: [EntrySortInput!] @cost(weight: "10")
where: EntryFilterInput @cost(weight: "10") where: EntryFilterInput @cost(weight: "10")
): EntriesCollectionSegment ): EntriesCollectionSegment
@ -180,7 +181,7 @@ type Query {
@cost(weight: "10") @cost(weight: "10")
entriesV2( entriesV2(
search: String search: String
popular: Boolean includeDefaults: Boolean
"Returns the first _n_ elements from the list." "Returns the first _n_ elements from the list."
first: Int first: Int
"Returns the elements in the list that come after the specified cursor." "Returns the elements in the list that come after the specified cursor."
@ -273,12 +274,14 @@ input CategoryFilterInput {
id: LongOperationFilterInput id: LongOperationFilterInput
name: StringOperationFilterInput name: StringOperationFilterInput
icon: StringOperationFilterInput icon: StringOperationFilterInput
entryType: NullableOfEntryTypeOperationFilterInput
} }
input CategorySortInput { input CategorySortInput {
id: SortEnumType @cost(weight: "10") id: SortEnumType @cost(weight: "10")
name: SortEnumType @cost(weight: "10") name: SortEnumType @cost(weight: "10")
icon: SortEnumType @cost(weight: "10") icon: SortEnumType @cost(weight: "10")
entryType: SortEnumType @cost(weight: "10")
} }
input CreateEntryInput { input CreateEntryInput {
@ -509,6 +512,13 @@ input LongOperationFilterInput {
nlte: Long @cost(weight: "10") nlte: Long @cost(weight: "10")
} }
input NullableOfEntryTypeOperationFilterInput {
eq: EntryType @cost(weight: "10")
neq: EntryType @cost(weight: "10")
in: [EntryType] @cost(weight: "10")
nin: [EntryType] @cost(weight: "10")
}
input NullableOfKeyboardLayoutTypeOperationFilterInput { input NullableOfKeyboardLayoutTypeOperationFilterInput {
eq: KeyboardLayoutType @cost(weight: "10") eq: KeyboardLayoutType @cost(weight: "10")
neq: KeyboardLayoutType @cost(weight: "10") neq: KeyboardLayoutType @cost(weight: "10")