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:
parent
3b2d799bfc
commit
b00f5ca73a
@ -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 />
|
||||
|
||||
@ -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('/');
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 -->
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user