1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Workshop - Added indicator for official submissions

Router - Reload previous screen if navigation is cancelled
Profile editor - Disable auto-update when editing workshop profiles
This commit is contained in:
Robert 2024-07-20 22:34:41 +02:00
parent 3b2d799bfc
commit b00f5ca73a
14 changed files with 127 additions and 42 deletions

View File

@ -50,6 +50,22 @@ namespace Artemis.Storage.Migrations
type: "TEXT", type: "TEXT",
nullable: false, nullable: false,
defaultValue: ""); defaultValue: "");
// Enable auto-update on all entries that are not profiles
migrationBuilder.Sql("UPDATE Entries SET AutoUpdate = 1 WHERE EntryType != 2");
// Enable auto-update on all entries of profiles that are fresh imports
migrationBuilder.Sql("""
UPDATE Entries
SET AutoUpdate = 1
WHERE EntryType = 2
AND EXISTS (
SELECT 1
FROM ProfileContainers
WHERE json_extract(ProfileContainers.Profile, '$.Id') = json_extract(Entries.Metadata, '$.ProfileId')
AND json_extract(ProfileContainers.Profile, '$.IsFreshImport') = 1
);
""");
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -128,8 +128,12 @@ 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

@ -173,11 +173,6 @@ public interface IProfileEditorService : IArtemisSharedUIService
/// <returns>The command scope that will group any commands until disposed.</returns> /// <returns>The command scope that will group any commands until disposed.</returns>
ProfileEditorCommandScope CreateCommandScope(string name); ProfileEditorCommandScope CreateCommandScope(string name);
/// <summary>
/// Saves the current profile.
/// </summary>
void SaveProfile();
/// <summary> /// <summary>
/// Asynchronously saves the current profile. /// Asynchronously saves the current profile.
/// </summary> /// </summary>

View File

@ -391,19 +391,12 @@ internal class ProfileEditorService : IProfileEditorService
_pixelsPerSecondSubject.OnNext(pixelsPerSecond); _pixelsPerSecondSubject.OnNext(pixelsPerSecond);
} }
/// <inheritdoc />
public void SaveProfile()
{
Profile? profile = _profileConfigurationSubject.Value?.Profile;
if (profile != null)
_profileService.SaveProfile(profile, true);
}
/// <inheritdoc /> /// <inheritdoc />
public async Task SaveProfileAsync() public async Task SaveProfileAsync()
{ {
await Task.Run(SaveProfile); Profile? profile = _profileConfigurationSubject.Value?.Profile;
if (profile != null)
await Task.Run(() => _profileService.SaveProfile(profile, true));
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -12,7 +12,6 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands; using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Avalonia.Threading;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.DataBinding; namespace Artemis.UI.Screens.ProfileEditor.Properties.DataBinding;
@ -23,9 +22,9 @@ public class DataBindingViewModel : ActivatableViewModelBase
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private ObservableAsPropertyHelper<bool>? _dataBindingEnabled; private ObservableAsPropertyHelper<bool>? _dataBindingEnabled;
private bool _editorOpen;
private ObservableAsPropertyHelper<ILayerProperty?>? _layerProperty; private ObservableAsPropertyHelper<ILayerProperty?>? _layerProperty;
private ObservableAsPropertyHelper<NodeScriptViewModel?>? _nodeScriptViewModel; private ObservableAsPropertyHelper<NodeScriptViewModel?>? _nodeScriptViewModel;
private bool _editorOpen;
private bool _playing; private bool _playing;
public DataBindingViewModel(IProfileEditorService profileEditorService, INodeVmFactory nodeVmFactory, IWindowService windowService, ISettingsService settingsService) public DataBindingViewModel(IProfileEditorService profileEditorService, INodeVmFactory nodeVmFactory, IWindowService windowService, ISettingsService settingsService)
@ -106,6 +105,6 @@ public class DataBindingViewModel : ActivatableViewModelBase
private void Save() private void Save()
{ {
if (!_editorOpen) if (!_editorOpen)
_profileEditorService.SaveProfile(); _profileEditorService.SaveProfileAsync();
} }
} }

View File

@ -15,8 +15,11 @@ 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;
@ -30,6 +33,8 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
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;
@ -48,12 +53,16 @@ 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);
@ -195,6 +204,23 @@ 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;
} }

View File

@ -59,7 +59,17 @@
Text="{CompiledBinding Entry.Name, FallbackValue=Title}" Text="{CompiledBinding Entry.Name, FallbackValue=Title}"
Margin="0 15" /> Margin="0 15" />
<StackPanel Orientation="Horizontal">
<TextBlock Classes="subtitle" TextTrimming="CharacterEllipsis" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" /> <TextBlock Classes="subtitle" TextTrimming="CharacterEllipsis" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
<avalonia:MaterialIcon IsVisible="{CompiledBinding Entry.IsOfficial}"
Kind="ShieldStar"
Foreground="{DynamicResource SystemAccentColorLight1}"
Margin="2 0 0 0"
Width="18"
Height="18"
HorizontalAlignment="Left"
ToolTip.Tip="Official entry by the Artemis team" />
</StackPanel>
<TextBlock Margin="0 8" TextWrapping="Wrap" Text="{CompiledBinding Entry.Summary, FallbackValue=Summary}" /> <TextBlock Margin="0 8" TextWrapping="Wrap" Text="{CompiledBinding Entry.Summary, FallbackValue=Summary}" />

View File

@ -37,11 +37,23 @@
<!-- Body --> <!-- Body -->
<Grid Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto"> <Grid Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto">
<TextBlock Grid.Row="0" Margin="0 0 0 5" TextTrimming="CharacterEllipsis"> <StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Margin="0 0 0 5" TextTrimming="CharacterEllipsis">
<Run Classes="h5" Text="{CompiledBinding Entry.Name, FallbackValue=Title}" /> <Run Classes="h5" Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
<Run Classes="subtitle">by</Run> <Run Classes="subtitle">by</Run>
<Run Classes="subtitle" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" /> <Run Classes="subtitle" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
</TextBlock> </TextBlock>
<avalonia:MaterialIcon
IsVisible="{CompiledBinding Entry.IsOfficial}"
Kind="ShieldStar"
Foreground="{DynamicResource SystemAccentColorLight1}"
Margin="2 -2 0 0"
Width="18"
Height="18"
HorizontalAlignment="Left"
ToolTip.Tip="Official entry by the Artemis team" />
</StackPanel>
<TextBlock Grid.Row="1" <TextBlock Grid.Row="1"
Classes="subtitle" Classes="subtitle"
TextWrapping="Wrap" TextWrapping="Wrap"
@ -79,11 +91,11 @@
<!-- Install state --> <!-- Install state -->
<StackPanel Grid.Column="2" Grid.Row="1" Margin="0 0 4 0" HorizontalAlignment="Right" VerticalAlignment="Bottom" IsVisible="{CompiledBinding IsInstalled}"> <StackPanel Grid.Column="2" Grid.Row="1" Margin="0 0 4 0" HorizontalAlignment="Right" VerticalAlignment="Bottom" IsVisible="{CompiledBinding IsInstalled}">
<TextBlock TextAlignment="Right" IsVisible="{CompiledBinding !UpdateAvailable}"> <TextBlock TextAlignment="Right" IsVisible="{CompiledBinding !UpdateAvailable}">
<avalonia:MaterialIcon Kind="CheckCircle" Foreground="{DynamicResource SystemAccentColorLight1}" Width="20" Height="20"/> <avalonia:MaterialIcon Kind="CheckCircle" Foreground="{DynamicResource SystemAccentColorLight1}" Width="20" Height="20" />
<Run>installed</Run> <Run>installed</Run>
</TextBlock> </TextBlock>
<TextBlock TextAlignment="Right" IsVisible="{CompiledBinding UpdateAvailable}"> <TextBlock TextAlignment="Right" IsVisible="{CompiledBinding UpdateAvailable}">
<avalonia:MaterialIcon Kind="Update" Foreground="{DynamicResource SystemAccentColorLight1}" Width="20" Height="20"/> <avalonia:MaterialIcon Kind="Update" Foreground="{DynamicResource SystemAccentColorLight1}" Width="20" Height="20" />
<Run>update available</Run> <Run>update available</Run>
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>

View File

@ -127,8 +127,10 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
await Manage(); await Manage();
} }
else if (!_cts.IsCancellationRequested) else if (!_cts.IsCancellationRequested)
{
_notificationService.CreateNotification().WithTitle("Installation failed").WithMessage(result.Message).WithSeverity(NotificationSeverity.Error).Show(); _notificationService.CreateNotification().WithTitle("Installation failed").WithMessage(result.Message).WithSeverity(NotificationSeverity.Error).Show();
} }
}
catch (Exception e) catch (Exception e)
{ {
_windowService.ShowExceptionDialog("Failed to install workshop entry", e); _windowService.ShowExceptionDialog("Failed to install workshop entry", e);

View File

@ -35,11 +35,22 @@
<!-- Body --> <!-- Body -->
<Grid Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto"> <Grid Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" VerticalAlignment="Stretch" RowDefinitions="Auto,*,Auto">
<TextBlock Grid.Row="0" Margin="0 0 0 5" TextTrimming="CharacterEllipsis"> <StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Margin="0 0 0 5" TextTrimming="CharacterEllipsis">
<Run Classes="h5" Text="{CompiledBinding Entry.Name, FallbackValue=Title}" /> <Run Classes="h5" Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
<Run Classes="subtitle">by</Run> <Run Classes="subtitle">by</Run>
<Run Classes="subtitle" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" /> <Run Classes="subtitle" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
</TextBlock> </TextBlock>
<avalonia:MaterialIcon
IsVisible="{CompiledBinding Entry.IsOfficial}"
Kind="ShieldStar"
Foreground="{DynamicResource SystemAccentColorLight1}"
Margin="2 -2 0 0"
Width="18"
Height="18"
HorizontalAlignment="Left"
ToolTip.Tip="Official entry by the Artemis team" />
</StackPanel>
<TextBlock Grid.Row="1" <TextBlock Grid.Row="1"
Classes="subtitle" Classes="subtitle"
TextWrapping="Wrap" TextWrapping="Wrap"

View File

@ -11,6 +11,7 @@ public class InstalledEntry : CorePropertyChanged, IEntrySummary
private Dictionary<string, JsonNode> _metadata = new(); private Dictionary<string, JsonNode> _metadata = new();
private long _id; private long _id;
private string _author; private string _author;
private bool _isOfficial;
private string _name; private string _name;
private string _summary; private string _summary;
private EntryType _entryType; private EntryType _entryType;
@ -175,6 +176,7 @@ public class InstalledEntry : CorePropertyChanged, IEntrySummary
{ {
Id = entry.Id; Id = entry.Id;
Author = entry.Author; Author = entry.Author;
IsOfficial = entry.IsOfficial;
Name = entry.Name; Name = entry.Name;
Summary = entry.Summary; Summary = entry.Summary;
EntryType = entry.EntryType; EntryType = entry.EntryType;
@ -200,6 +202,13 @@ public class InstalledEntry : CorePropertyChanged, IEntrySummary
private set => SetAndNotify(ref _author, value); private set => SetAndNotify(ref _author, value);
} }
/// <inheritdoc />
public bool IsOfficial
{
get => _isOfficial;
private set => SetAndNotify(ref _isOfficial, value);
}
/// <inheritdoc /> /// <inheritdoc />
public string Name public string Name
{ {

View File

@ -31,6 +31,7 @@ fragment submittedEntry on Entry {
fragment entrySummary on Entry { fragment entrySummary on Entry {
id id
author author
isOfficial
name name
summary summary
entryType entryType
@ -45,6 +46,7 @@ fragment entrySummary on Entry {
fragment entryDetails on Entry { fragment entryDetails on Entry {
id id
author author
isOfficial
name name
summary summary
entryType entryType

View File

@ -2,7 +2,7 @@ schema: schema.graphql
extensions: extensions:
endpoints: endpoints:
Default GraphQL Endpoint: Default GraphQL Endpoint:
url: https://localhost:7281/graphql url: https://workshop.artemis-rgb.com/graphql
headers: headers:
user-agent: JS GraphQL user-agent: JS GraphQL
introspect: true introspect: true

View File

@ -61,6 +61,7 @@ type Entry {
iconId: UUID iconId: UUID
id: Long! id: Long!
images: [Image!]! images: [Image!]!
isOfficial: Boolean!
latestRelease: Release latestRelease: Release
latestReleaseId: Long latestReleaseId: Long
layoutInfo: [LayoutInfo!]! layoutInfo: [LayoutInfo!]!
@ -124,6 +125,7 @@ type PluginInfo {
entry: Entry! entry: Entry!
entryId: Long! entryId: Long!
helpPage: String helpPage: String
minmumVersion: String
pluginGuid: UUID! pluginGuid: UUID!
repository: String repository: String
requiresAdmin: Boolean! requiresAdmin: Boolean!
@ -310,6 +312,7 @@ input EntryFilterInput {
iconId: UuidOperationFilterInput iconId: UuidOperationFilterInput
id: LongOperationFilterInput id: LongOperationFilterInput
images: ListFilterInputTypeOfImageFilterInput images: ListFilterInputTypeOfImageFilterInput
isOfficial: BooleanOperationFilterInput
latestRelease: ReleaseFilterInput latestRelease: ReleaseFilterInput
latestReleaseId: LongOperationFilterInput latestReleaseId: LongOperationFilterInput
layoutInfo: ListFilterInputTypeOfLayoutInfoFilterInput layoutInfo: ListFilterInputTypeOfLayoutInfoFilterInput
@ -331,6 +334,7 @@ input EntrySortInput {
icon: ImageSortInput icon: ImageSortInput
iconId: SortEnumType iconId: SortEnumType
id: SortEnumType id: SortEnumType
isOfficial: SortEnumType
latestRelease: ReleaseSortInput latestRelease: ReleaseSortInput
latestReleaseId: SortEnumType latestReleaseId: SortEnumType
name: SortEnumType name: SortEnumType
@ -479,6 +483,7 @@ input PluginInfoFilterInput {
entry: EntryFilterInput entry: EntryFilterInput
entryId: LongOperationFilterInput entryId: LongOperationFilterInput
helpPage: StringOperationFilterInput helpPage: StringOperationFilterInput
minmumVersion: StringOperationFilterInput
or: [PluginInfoFilterInput!] or: [PluginInfoFilterInput!]
pluginGuid: UuidOperationFilterInput pluginGuid: UuidOperationFilterInput
repository: StringOperationFilterInput repository: StringOperationFilterInput
@ -494,6 +499,7 @@ input PluginInfoSortInput {
entry: EntrySortInput entry: EntrySortInput
entryId: SortEnumType entryId: SortEnumType
helpPage: SortEnumType helpPage: SortEnumType
minmumVersion: SortEnumType
pluginGuid: SortEnumType pluginGuid: SortEnumType
repository: SortEnumType repository: SortEnumType
requiresAdmin: SortEnumType requiresAdmin: SortEnumType