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>
/// The full path to the Artemis data folder
/// </summary>
#if DEBUG
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis-dev");
#else
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis");
#endif
/// <summary>
/// The full path to the Artemis logs folder

View File

@ -119,7 +119,7 @@ public partial class PluginViewModel : ActivatableViewModelBase
Enabling = true;
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)
return;
}

View File

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

View File

@ -41,7 +41,7 @@ public partial class DefaultEntriesStepViewModel : WizardStepViewModel
_client = client;
_getDefaultEntryItemViewModel = getDefaultEntryItemViewModel;
_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);
ContinueText = "Install selected entries";

View File

@ -31,7 +31,6 @@ public partial class DefaultEntryItemViewModel : ActivatableViewModelBase
[Notify] private bool _isInstalled;
[Notify] private bool _shouldInstall;
[Notify] private bool _enabling;
[Notify] private float _installProgress;
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
else if (result.Entry?.EntryType == EntryType.Plugin)
{
Enabling = true;
await EnablePluginAndFeatures(result.Entry);
Enabling = false;
}
return result.IsSuccess;

View File

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

View File

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

View File

@ -22,6 +22,7 @@
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Grid.RowSpan="3"
Spacing="10"
Margin="0 0 10 0"
VerticalAlignment="Top"
HorizontalAlignment="Right"
@ -32,20 +33,16 @@
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Categories</TextBlock>
<Border Classes="card-separator" />
<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>
</Border>
<Border Classes="card" VerticalAlignment="Stretch">
<StackPanel>
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Categories</TextBlock>
<Border Classes="card-separator" />
<ContentControl Content="{CompiledBinding CategoriesViewModel}"></ContentControl>
<CheckBox IsChecked="{CompiledBinding IncludeDefaultEntries}">
<StackPanel Orientation="Horizontal" Spacing="2">
<TextBlock>Include default entries</TextBlock>
</StackPanel>
</CheckBox>
</StackPanel>
</Border>
</StackPanel>

View File

@ -25,7 +25,7 @@ public partial class EntryListViewModel : RoutableScreen
private readonly SourceList<IEntrySummary> _entries = new();
private readonly INotificationService _notificationService;
private readonly IWorkshopClient _workshopClient;
private IGetEntriesv2_EntriesV2_PageInfo? _currentPageInfo;
private IGetEntries_EntriesV2_PageInfo? _currentPageInfo;
[Notify] private bool _initializing = true;
[Notify] private bool _fetchingMore;
@ -33,17 +33,19 @@ public partial class EntryListViewModel : RoutableScreen
[Notify] private bool _includeDefaultEntries;
[Notify] private Vector _scrollOffset;
protected EntryListViewModel(IWorkshopClient workshopClient,
CategoriesViewModel categoriesViewModel,
protected EntryListViewModel(EntryType entryType,
IWorkshopClient workshopClient,
EntryListInputViewModel entryListInputViewModel,
INotificationService notificationService,
Func<EntryType, CategoriesViewModel> getCategoriesViewModel,
Func<IEntrySummary, EntryListItemViewModel> getEntryListViewModel)
{
_workshopClient = workshopClient;
_notificationService = notificationService;
CategoriesViewModel = categoriesViewModel;
CategoriesViewModel = getCategoriesViewModel(entryType);
InputViewModel = entryListInputViewModel;
EntryType = entryType;
_entries.Connect()
.Transform(getEntryListViewModel)
@ -75,7 +77,7 @@ public partial class EntryListViewModel : RoutableScreen
public CategoriesViewModel CategoriesViewModel { get; }
public EntryListInputViewModel InputViewModel { get; }
public bool ShowCategoryFilter { get; set; } = true;
public EntryType? EntryType { get; set; }
public EntryType EntryType { get; }
public ReadOnlyObservableCollection<EntryListItemViewModel> Entries { get; }
@ -93,7 +95,7 @@ public partial class EntryListViewModel : RoutableScreen
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();
_currentPageInfo = entries.Data?.EntriesV2?.PageInfo;
@ -124,7 +126,6 @@ public partial class EntryListViewModel : RoutableScreen
And =
[
new EntryFilterInput {EntryType = new EntryTypeOperationFilterInput {Eq = EntryType}},
new EntryFilterInput {DefaultEntryInfo = IncludeDefaultEntries ? new DefaultEntryInfoFilterInput {EntryId = new LongOperationFilterInput {Ngt = 0}} : null},
..CategoriesViewModel.CategoryFilters ?? []
]
};

View File

@ -51,7 +51,7 @@ public partial class WorkshopHomeViewModel : RoutableScreen
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 =>
{
l.Clear();

View File

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

View File

@ -109,6 +109,7 @@ public partial class SubmissionDetailsViewModel : RoutableScreen
specificationsViewModel.IsEssential = Entry.DefaultEntryInfo?.IsEssential ?? false;
specificationsViewModel.IsDeviceProvider = Entry.DefaultEntryInfo?.IsDeviceProvider ?? false;
specificationsViewModel.PreselectedCategories = Entry.Categories.Select(c => c.Id).ToList();
specificationsViewModel.EntryType = Entry.EntryType;
specificationsViewModel.Tags.Clear();
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.Shared.Routing;
using Artemis.WebClient.Workshop;
@ -9,9 +10,8 @@ public class PluginListViewModel : RoutableHostScreen<RoutableScreen>
private readonly EntryListViewModel _entryListViewModel;
public override RoutableScreen DefaultScreen => _entryListViewModel;
public PluginListViewModel(EntryListViewModel entryListViewModel)
public PluginListViewModel(Func<EntryType, EntryListViewModel> getEntryListViewModel)
{
_entryListViewModel = entryListViewModel;
_entryListViewModel.EntryType = EntryType.Plugin;
_entryListViewModel = getEntryListViewModel(EntryType.Plugin);
}
}

View File

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

View File

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

View File

@ -38,7 +38,7 @@ public partial class SubmitStepViewModel : SubmissionViewModel
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 {
categories(order: {name: ASC}) {
query GetCategories($entryType: EntryType) {
categories(where: {entryType: {in: [$entryType, null]}} order: {name: ASC}) {
id
name
icon

View File

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

View File

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