1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 13:28:33 +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",
nullable: false,
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 />

View File

@ -79,7 +79,7 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
path = NavigateUp(_currentRouteSubject.Value, path);
else
path = path.ToLower().Trim(' ', '/', '\\');
options ??= new RouterNavigationOptions();
// Routing takes place on the UI thread with processing heavy tasks offloaded by the router itself
@ -90,7 +90,7 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
public async Task Reload()
{
string path = _currentRouteSubject.Value ?? "blank";
// Routing takes place on the UI thread with processing heavy tasks offloaded by the router itself
await Dispatcher.UIThread.InvokeAsync(async () =>
{
@ -128,8 +128,12 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
await navigation.Navigate(args);
// 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)
{
await Reload();
return;
}
if (options.AddToHistory && previousPath != null)
{
@ -172,7 +176,7 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
public async Task<bool> GoUp(RouterNavigationOptions? options = null)
{
string? currentPath = _currentRouteSubject.Value;
// Keep removing segments until we find a parent route that resolves
while (currentPath != null && currentPath.Contains('/'))
{
@ -223,8 +227,8 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable
_logger.Debug("Router disposed, should that be? Stacktrace: \r\n{StackTrace}", Environment.StackTrace);
}
private string NavigateUp(string current, string path)
{
string[] pathParts = current.Split('/');

View File

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

View File

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

View File

@ -12,7 +12,6 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.DataBinding;
@ -23,9 +22,9 @@ public class DataBindingViewModel : ActivatableViewModelBase
private readonly IProfileEditorService _profileEditorService;
private readonly IWindowService _windowService;
private ObservableAsPropertyHelper<bool>? _dataBindingEnabled;
private bool _editorOpen;
private ObservableAsPropertyHelper<ILayerProperty?>? _layerProperty;
private ObservableAsPropertyHelper<NodeScriptViewModel?>? _nodeScriptViewModel;
private bool _editorOpen;
private bool _playing;
public DataBindingViewModel(IProfileEditorService profileEditorService, INodeVmFactory nodeVmFactory, IWindowService windowService, ISettingsService settingsService)
@ -106,6 +105,6 @@ public class DataBindingViewModel : ActivatableViewModelBase
private void Save()
{
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.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.MainWindow;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.WebClient.Workshop.Models;
using Artemis.WebClient.Workshop.Services;
using DynamicData;
using DynamicData.Binding;
using PropertyChanged.SourceGenerator;
@ -30,10 +33,12 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
private readonly IProfileService _profileService;
private readonly ISettingsService _settingsService;
private readonly IMainWindowService _mainWindowService;
private readonly IWorkshopService _workshopService;
private readonly IWindowService _windowService;
private readonly SourceList<IToolViewModel> _tools;
private ObservableAsPropertyHelper<ProfileEditorHistory?>? _history;
private ObservableAsPropertyHelper<bool>? _suspendedEditing;
[Notify] private ProfileConfiguration? _profileConfiguration;
/// <inheritdoc />
@ -48,12 +53,16 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
StatusBarViewModel statusBarViewModel,
IEnumerable<IToolViewModel> toolViewModels,
IMainWindowService mainWindowService,
IInputService inputService)
IInputService inputService,
IWorkshopService workshopService,
IWindowService windowService)
{
_profileService = profileService;
_profileEditorService = profileEditorService;
_settingsService = settingsService;
_mainWindowService = mainWindowService;
_workshopService = workshopService;
_windowService = windowService;
_tools = new SourceList<IToolViewModel>();
_tools.AddRange(toolViewModels);
@ -144,7 +153,7 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
{
if (!Shared.UI.KeyBindingsEnabled || !_mainWindowService.IsMainWindowFocused)
return;
if (e.Modifiers == KeyboardModifierKey.Control && e.Key == KeyboardKey.Z)
History?.Undo.Execute().Subscribe();
else if (e.Modifiers == KeyboardModifierKey.Control && e.Key == KeyboardKey.Y)
@ -195,6 +204,23 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
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);
ProfileConfiguration = profileConfiguration;
}

View File

@ -59,8 +59,18 @@
Text="{CompiledBinding Entry.Name, FallbackValue=Title}"
Margin="0 15" />
<TextBlock Classes="subtitle" TextTrimming="CharacterEllipsis" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
<StackPanel Orientation="Horizontal">
<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}" />
<!-- Categories -->

View File

@ -37,11 +37,23 @@
<!-- Body -->
<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">
<Run Classes="h5" Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
<Run Classes="subtitle">by</Run>
<Run Classes="subtitle" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
</TextBlock>
<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="subtitle">by</Run>
<Run Classes="subtitle" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
</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"
Classes="subtitle"
TextWrapping="Wrap"
@ -75,15 +87,15 @@
<Run>downloads</Run>
</TextBlock>
</StackPanel>
<!-- Install state -->
<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}">
<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>
</TextBlock>
<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>
</TextBlock>
</StackPanel>

View File

@ -127,7 +127,9 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
await Manage();
}
else if (!_cts.IsCancellationRequested)
{
_notificationService.CreateNotification().WithTitle("Installation failed").WithMessage(result.Message).WithSeverity(NotificationSeverity.Error).Show();
}
}
catch (Exception e)
{

View File

@ -35,11 +35,22 @@
<!-- Body -->
<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">
<Run Classes="h5" Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
<Run Classes="subtitle">by</Run>
<Run Classes="subtitle" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
</TextBlock>
<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="subtitle">by</Run>
<Run Classes="subtitle" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
</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"
Classes="subtitle"
TextWrapping="Wrap"

View File

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

View File

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

View File

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

View File

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