diff --git a/.github/workflows/docfx.yml b/.github/workflows/docfx.yml index a26bf9032..bd3301f37 100644 --- a/.github/workflows/docfx.yml +++ b/.github/workflows/docfx.yml @@ -33,4 +33,4 @@ jobs: username: ${{ secrets.FTP_USER }} password: ${{ secrets.FTP_PASSWORD }} local-dir: docfx/docfx_project/_site/ - server-dir: /httpdocs/docs/ + server-dir: /docs/ diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs index ea1a85825..681ddb923 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs @@ -158,7 +158,16 @@ public class DataModelPath : IStorageModel, IDisposable if (_disposed) throw new ObjectDisposedException("DataModelPath"); - return Segments.LastOrDefault()?.GetPropertyType(); + // Prefer the actual type from the segments + Type? segmentType = Segments.LastOrDefault()?.GetPropertyType(); + if (segmentType != null) + return segmentType; + + // Fall back to stored type + if (!string.IsNullOrWhiteSpace(Entity.Type)) + return Type.GetType(Entity.Type); + + return null; } /// @@ -358,9 +367,14 @@ public class DataModelPath : IStorageModel, IDisposable // Do not save an invalid state if (!IsValid) return; - + Entity.Path = Path; Entity.DataModelId = DataModelId; + + // Store the type name but only if available + Type? pathType = Segments.LastOrDefault()?.GetPropertyType(); + if (pathType != null) + Entity.Type = pathType.FullName; } #region Equality members diff --git a/src/Artemis.Core/Utilities/StringUtilities.cs b/src/Artemis.Core/Utilities/StringUtilities.cs new file mode 100644 index 000000000..aba045180 --- /dev/null +++ b/src/Artemis.Core/Utilities/StringUtilities.cs @@ -0,0 +1,150 @@ +using System.Text; + +namespace Artemis.Core; + +/// +/// Provides some random string utilities. +/// +public static class StringUtilities +{ + /// + /// Produces optional, URL-friendly version of a title, "like-this-one". + /// hand-tuned for speed, reflects performance refactoring contributed + /// by John Gietzen (user otac0n) + /// + /// Source: https://stackoverflow.com/a/25486 + public static string UrlFriendly(string? title) + { + if (title == null) return ""; + + const int maxlen = 80; + int len = title.Length; + bool prevdash = false; + StringBuilder sb = new(len); + char c; + + for (int i = 0; i < len; i++) + { + c = title[i]; + if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) + { + sb.Append(c); + prevdash = false; + } + else if (c >= 'A' && c <= 'Z') + { + // tricky way to convert to lowercase + sb.Append((char) (c | 32)); + prevdash = false; + } + else if (c == ' ' || c == ',' || c == '.' || c == '/' || + c == '\\' || c == '-' || c == '_' || c == '=') + { + if (!prevdash && sb.Length > 0) + { + sb.Append('-'); + prevdash = true; + } + } + else if (c >= 128) + { + int prevlen = sb.Length; + sb.Append(RemapInternationalCharToAscii(c)); + if (prevlen != sb.Length) prevdash = false; + } + + if (i == maxlen) break; + } + + if (prevdash) + return sb.ToString().Substring(0, sb.Length - 1); + return sb.ToString(); + } + + /// + /// Remaps internation characters to their ASCII equivalent. + /// Source: https://meta.stackexchange.com/a/7696 + /// + /// The character to remap + /// The ASCII equivalent. + public static string RemapInternationalCharToAscii(char c) + { + string s = c.ToString().ToLowerInvariant(); + if ("àåáâäãåą".Contains(s)) + { + return "a"; + } + else if ("èéêëę".Contains(s)) + { + return "e"; + } + else if ("ìíîïı".Contains(s)) + { + return "i"; + } + else if ("òóôõöøőð".Contains(s)) + { + return "o"; + } + else if ("ùúûüŭů".Contains(s)) + { + return "u"; + } + else if ("çćčĉ".Contains(s)) + { + return "c"; + } + else if ("żźž".Contains(s)) + { + return "z"; + } + else if ("śşšŝ".Contains(s)) + { + return "s"; + } + else if ("ñń".Contains(s)) + { + return "n"; + } + else if ("ýÿ".Contains(s)) + { + return "y"; + } + else if ("ğĝ".Contains(s)) + { + return "g"; + } + else if (c == 'ř') + { + return "r"; + } + else if (c == 'ł') + { + return "l"; + } + else if (c == 'đ') + { + return "d"; + } + else if (c == 'ß') + { + return "ss"; + } + else if (c == 'Þ') + { + return "th"; + } + else if (c == 'ĥ') + { + return "h"; + } + else if (c == 'ĵ') + { + return "j"; + } + else + { + return ""; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/DataModelPathEntity.cs b/src/Artemis.Storage/Entities/Profile/DataModelPathEntity.cs index 780c4800c..ef585ce45 100644 --- a/src/Artemis.Storage/Entities/Profile/DataModelPathEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/DataModelPathEntity.cs @@ -4,4 +4,5 @@ public class DataModelPathEntity { public string Path { get; set; } public string DataModelId { get; set; } + public string Type { get; set; } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Providers/IProtocolProvider.cs b/src/Artemis.UI.Shared/Providers/IProtocolProvider.cs new file mode 100644 index 000000000..145f0aa91 --- /dev/null +++ b/src/Artemis.UI.Shared/Providers/IProtocolProvider.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; + +namespace Artemis.UI.Shared.Providers; + +/// +/// Represents a provider associating with a custom protocol, e.g. artemis:// +/// +public interface IProtocolProvider +{ + /// + /// Associate Artemis with the provided custom protocol. + /// + /// The protocol to associate Artemis with. + Task AssociateWithProtocol(string protocol); + + /// + /// Disassociate Artemis with the provided custom protocol. + /// + /// The protocol to disassociate Artemis with. + Task DisassociateWithProtocol(string protocol); +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Routing/Router/Router.cs b/src/Artemis.UI.Shared/Routing/Router/Router.cs index 540cf25e8..a4afe117d 100644 --- a/src/Artemis.UI.Shared/Routing/Router/Router.cs +++ b/src/Artemis.UI.Shared/Routing/Router/Router.cs @@ -72,6 +72,7 @@ internal class Router : CorePropertyChanged, IRouter, IDisposable /// public async Task Navigate(string path, RouterNavigationOptions? options = null) { + path = path.ToLower().Trim(' ', '/', '\\'); options ??= new RouterNavigationOptions(); // Routing takes place on the UI thread with processing heavy tasks offloaded by the router itself diff --git a/src/Artemis.UI.Windows/App.axaml.cs b/src/Artemis.UI.Windows/App.axaml.cs index 9854a1ca5..41ed8e923 100644 --- a/src/Artemis.UI.Windows/App.axaml.cs +++ b/src/Artemis.UI.Windows/App.axaml.cs @@ -74,6 +74,9 @@ public class App : Application return false; } + + string? route = (ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.Args?.FirstOrDefault(a => a.Contains("route")); + route = route?.Split("artemis://")[1]; string url = File.ReadAllText(Path.Combine(Constants.DataFolder, "webserver.txt")); using HttpClient client = new(); try @@ -81,7 +84,7 @@ public class App : Application CancellationTokenSource cts = new(); cts.CancelAfter(2000); - HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground"), cts.Token); + HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground") {Content = new StringContent(route ?? "")}, cts.Token); httpResponseMessage.EnsureSuccessStatusCode(); return true; } diff --git a/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs b/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs index 337e21744..a2906d46c 100644 --- a/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs +++ b/src/Artemis.UI.Windows/DryIoc/ContainerExtensions.cs @@ -24,5 +24,6 @@ public static class UIContainerExtensions container.Register(); container.Register(serviceKey: WindowsInputProvider.Id); container.Register(); + container.Register(); } } \ No newline at end of file diff --git a/src/Artemis.UI.Windows/Providers/ProtocolProvider.cs b/src/Artemis.UI.Windows/Providers/ProtocolProvider.cs new file mode 100644 index 000000000..c3ec1622f --- /dev/null +++ b/src/Artemis.UI.Windows/Providers/ProtocolProvider.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading.Tasks; +using Artemis.Core; +using Artemis.UI.Shared.Providers; +using Microsoft.Win32; + +namespace Artemis.UI.Windows.Providers; + +public class ProtocolProvider : IProtocolProvider +{ + /// + public async Task AssociateWithProtocol(string protocol) + { + string key = $"HKEY_CURRENT_USER\\Software\\Classes\\{protocol}"; + Registry.SetValue($"{key}", null, "URL:artemis protocol"); + Registry.SetValue($"{key}", "URL Protocol", ""); + Registry.SetValue($"{key}\\DefaultIcon", null, $"\"{Constants.ExecutablePath}\",1"); + Registry.SetValue($"{key}\\shell\\open\\command", null, $"\"{Constants.ExecutablePath}\", \"--route=%1\""); + } + + /// + public async Task DisassociateWithProtocol(string protocol) + { + try + { + string key = $"HKEY_CURRENT_USER\\Software\\Classes\\{protocol}"; + Registry.CurrentUser.DeleteSubKeyTree(key); + } + catch (ArgumentException) + { + // Ignore errors (which means that the protocol wasn't associated before) + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Controllers/RemoteController.cs b/src/Artemis.UI/Controllers/RemoteController.cs index abf1e7c89..6ace7f441 100644 --- a/src/Artemis.UI/Controllers/RemoteController.cs +++ b/src/Artemis.UI/Controllers/RemoteController.cs @@ -1,6 +1,8 @@ using System; +using System.IO; using Artemis.Core; using Artemis.Core.Services; +using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Services.MainWindow; using Avalonia.Threading; using EmbedIO; @@ -13,11 +15,13 @@ public class RemoteController : WebApiController { private readonly ICoreService _coreService; private readonly IMainWindowService _mainWindowService; + private readonly IRouter _router; - public RemoteController(ICoreService coreService, IMainWindowService mainWindowService) + public RemoteController(ICoreService coreService, IMainWindowService mainWindowService, IRouter router) { _coreService = coreService; _mainWindowService = mainWindowService; + _router = router; } [Route(HttpVerbs.Any, "/status")] @@ -29,7 +33,15 @@ public class RemoteController : WebApiController [Route(HttpVerbs.Post, "/remote/bring-to-foreground")] public void PostBringToForeground() { - Dispatcher.UIThread.Post(() => _mainWindowService.OpenMainWindow()); + using StreamReader reader = new(Request.InputStream); + string route = reader.ReadToEnd(); + + Dispatcher.UIThread.InvokeAsync(async () => + { + if (!string.IsNullOrWhiteSpace(route)) + await _router.Navigate(route); + _mainWindowService.OpenMainWindow(); + }); } [Route(HttpVerbs.Post, "/remote/restart")] diff --git a/src/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Artemis.UI/Screens/Root/RootViewModel.cs index 4f6580793..e1fb3eeb9 100644 --- a/src/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Artemis.UI/Screens/Root/RootViewModel.cs @@ -14,6 +14,7 @@ using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services.MainWindow; using Avalonia; +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Threading; using ReactiveUI; @@ -181,6 +182,9 @@ public class RootViewModel : RoutableHostScreen, IMainWindowProv } _lifeTime.MainWindow.Activate(); + if (_lifeTime.MainWindow.WindowState == WindowState.Minimized) + _lifeTime.MainWindow.WindowState = WindowState.Normal; + OnMainWindowOpened(); } diff --git a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml index 378f37b64..861293d66 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml @@ -45,6 +45,19 @@ + + + Associate with Artemis links + + Open Artemis when navigating to artemis:// links, allows opening workshop entries from your browser. + + + + + + + + Enable Mica effect @@ -57,7 +70,7 @@ - + Startup delay diff --git a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs index 74adce108..6e3ee0d0b 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs @@ -29,6 +29,7 @@ namespace Artemis.UI.Screens.Settings; public class GeneralTabViewModel : RoutableScreen { private readonly IAutoRunProvider? _autoRunProvider; + private readonly IProtocolProvider? _protocolProvider; private readonly IDebugService _debugService; private readonly PluginSetting _defaultLayerBrushDescriptor; private readonly INotificationService _notificationService; @@ -52,6 +53,7 @@ public class GeneralTabViewModel : RoutableScreen _updateService = updateService; _notificationService = notificationService; _autoRunProvider = container.Resolve(IfUnresolved.ReturnDefault); + _protocolProvider = container.Resolve(IfUnresolved.ReturnDefault); List layerBrushProviders = pluginManagementService.GetFeaturesOfType(); List graphicsContextProviders = container.Resolve>(); @@ -74,13 +76,16 @@ public class GeneralTabViewModel : RoutableScreen this.WhenActivated(d => { UIAutoRun.SettingChanged += UIAutoRunOnSettingChanged; + UIUseProtocol.SettingChanged += UIUseProtocolOnSettingChanged; UIAutoRunDelay.SettingChanged += UIAutoRunDelayOnSettingChanged; EnableMica.SettingChanged += EnableMicaOnSettingChanged; Dispatcher.UIThread.InvokeAsync(ApplyAutoRun); + Dispatcher.UIThread.Invoke(ApplyProtocolAssociation); Disposable.Create(() => { UIAutoRun.SettingChanged -= UIAutoRunOnSettingChanged; + UIUseProtocol.SettingChanged -= UIUseProtocolOnSettingChanged; UIAutoRunDelay.SettingChanged -= UIAutoRunDelayOnSettingChanged; EnableMica.SettingChanged -= EnableMicaOnSettingChanged; @@ -148,6 +153,7 @@ public class GeneralTabViewModel : RoutableScreen } public PluginSetting UIAutoRun => _settingsService.GetSetting("UI.AutoRun", false); + public PluginSetting UIUseProtocol => _settingsService.GetSetting("UI.UseProtocol", true); public PluginSetting UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15); public PluginSetting UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true); public PluginSetting EnableMica => _settingsService.GetSetting("UI.EnableMica", true); @@ -223,11 +229,34 @@ public class GeneralTabViewModel : RoutableScreen _windowService.ShowExceptionDialog("Failed to apply auto-run", exception); } } + + private void ApplyProtocolAssociation() + { + if (_protocolProvider == null) + return; + + try + { + if (UIUseProtocol.Value) + _protocolProvider.AssociateWithProtocol("artemis"); + else + _protocolProvider.DisassociateWithProtocol("artemis"); + } + catch (Exception exception) + { + _windowService.ShowExceptionDialog("Failed to apply protocol association", exception); + } + } private async void UIAutoRunOnSettingChanged(object? sender, EventArgs e) { await ApplyAutoRun(); } + + private void UIUseProtocolOnSettingChanged(object? sender, EventArgs e) + { + ApplyProtocolAssociation(); + } private async void UIAutoRunDelayOnSettingChanged(object? sender, EventArgs e) { diff --git a/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs index 05bbc060c..9661d7e23 100644 --- a/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs +++ b/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs @@ -20,6 +20,7 @@ namespace Artemis.UI.Screens.StartupWizard; public class StartupWizardViewModel : DialogViewModelBase { private readonly IAutoRunProvider? _autoRunProvider; + private readonly IProtocolProvider? _protocolProvider; private readonly IRgbService _rgbService; private readonly ISettingsService _settingsService; private readonly IWindowService _windowService; @@ -39,6 +40,7 @@ public class StartupWizardViewModel : DialogViewModelBase _rgbService = rgbService; _windowService = windowService; _autoRunProvider = container.Resolve(IfUnresolved.ReturnDefault); + _protocolProvider = container.Resolve(IfUnresolved.ReturnDefault); Continue = ReactiveCommand.Create(ExecuteContinue); GoBack = ReactiveCommand.Create(ExecuteGoBack); @@ -58,11 +60,13 @@ public class StartupWizardViewModel : DialogViewModelBase this.WhenActivated(d => { UIAutoRun.SettingChanged += UIAutoRunOnSettingChanged; + UIUseProtocol.SettingChanged += UIUseProtocolOnSettingChanged; UIAutoRunDelay.SettingChanged += UIAutoRunDelayOnSettingChanged; Disposable.Create(() => { UIAutoRun.SettingChanged -= UIAutoRunOnSettingChanged; + UIUseProtocol.SettingChanged -= UIUseProtocolOnSettingChanged; UIAutoRunDelay.SettingChanged -= UIAutoRunDelayOnSettingChanged; _settingsService.SaveAllSettings(); @@ -81,6 +85,7 @@ public class StartupWizardViewModel : DialogViewModelBase public bool IsAutoRunSupported => _autoRunProvider != null; public PluginSetting UIAutoRun => _settingsService.GetSetting("UI.AutoRun", false); + public PluginSetting UIUseProtocol => _settingsService.GetSetting("UI.UseProtocol", true); public PluginSetting UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15); public PluginSetting UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true); public PluginSetting UICheckForUpdates => _settingsService.GetSetting("UI.Updating.AutoCheck", true); @@ -177,11 +182,34 @@ public class StartupWizardViewModel : DialogViewModelBase } } + private void ApplyProtocolAssociation() + { + if (_protocolProvider == null) + return; + + try + { + if (UIUseProtocol.Value) + _protocolProvider.AssociateWithProtocol("artemis"); + else + _protocolProvider.DisassociateWithProtocol("artemis"); + } + catch (Exception exception) + { + _windowService.ShowExceptionDialog("Failed to apply protocol association", exception); + } + } + private async void UIAutoRunOnSettingChanged(object? sender, EventArgs e) { await ApplyAutoRun(); } + private void UIUseProtocolOnSettingChanged(object? sender, EventArgs e) + { + ApplyProtocolAssociation(); + } + private async void UIAutoRunDelayOnSettingChanged(object? sender, EventArgs e) { if (_autoRunProvider == null || !UIAutoRun.Value) diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml b/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml index de06af76b..d8955f74e 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStep.axaml @@ -42,6 +42,19 @@ + + + + Associate with Artemis links + + Open Artemis when navigating to artemis:// links, allows opening workshop entries from your browser. + + + + + + + diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml index e8dbea39a..2ca2544a5 100644 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml @@ -22,14 +22,24 @@ - - - + + + + + + + private readonly IWorkshopClient _client; private readonly ProfileEntryInstallationHandler _installationHandler; private readonly INotificationService _notificationService; - private readonly IWindowService _windowService; private readonly ObservableAsPropertyHelper _updatedAt; + private readonly IWindowService _windowService; private IGetEntryById_Entry? _entry; public ProfileDetailsViewModel(IWorkshopClient client, ProfileEntryInstallationHandler installationHandler, INotificationService notificationService, IWindowService windowService) @@ -34,8 +35,11 @@ public class ProfileDetailsViewModel : RoutableScreen _updatedAt = this.WhenAnyValue(vm => vm.Entry).Select(e => e?.LatestRelease?.CreatedAt ?? e?.CreatedAt).ToProperty(this, vm => vm.UpdatedAt); DownloadLatestRelease = ReactiveCommand.CreateFromTask(ExecuteDownloadLatestRelease); + CopyShareLink = ReactiveCommand.CreateFromTask(ExecuteCopyShareLink); } + public ReactiveCommand CopyShareLink { get; set; } + public ReactiveCommand DownloadLatestRelease { get; } public DateTimeOffset? UpdatedAt => _updatedAt.Value; @@ -75,4 +79,13 @@ public class ProfileDetailsViewModel : RoutableScreen else _notificationService.CreateNotification().WithTitle("Failed to install profile").WithMessage(result.Message).WithSeverity(NotificationSeverity.Error).Show(); } + + private async Task ExecuteCopyShareLink(CancellationToken arg) + { + if (Entry == null) + return; + + await Shared.UI.Clipboard.SetTextAsync($"{WorkshopConstants.WORKSHOP_URL}/entries/{Entry.Id}/{StringUtilities.UrlFriendly(Entry.Name)}"); + _notificationService.CreateNotification().WithTitle("Copied share link to clipboard.").Show(); + } } \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Branching/BooleanBranchNode.cs b/src/Artemis.VisualScripting/Nodes/Branching/BooleanBranchNode.cs index fdd7c5bcc..b07aea8cf 100644 --- a/src/Artemis.VisualScripting/Nodes/Branching/BooleanBranchNode.cs +++ b/src/Artemis.VisualScripting/Nodes/Branching/BooleanBranchNode.cs @@ -38,9 +38,9 @@ public class BooleanBranchNode : Node private void InputPinConnected(object? sender, SingleValueEventArgs e) { - if (TrueInput.ConnectedTo.Any() && !FalseInput.ConnectedTo.Any()) + if (TrueInput.ConnectedTo.Any()) ChangeType(TrueInput.ConnectedTo.First().Type); - if (FalseInput.ConnectedTo.Any() && !TrueInput.ConnectedTo.Any()) + else if (FalseInput.ConnectedTo.Any()) ChangeType(FalseInput.ConnectedTo.First().Type); } diff --git a/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelEventCycleNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelEventCycleNodeCustomViewModel.cs index 6e129a434..20247322b 100644 --- a/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelEventCycleNodeCustomViewModel.cs +++ b/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelEventCycleNodeCustomViewModel.cs @@ -38,7 +38,7 @@ public class DataModelEventCycleNodeCustomViewModel : CustomNodeViewModel // Subscribe to node changes _cycleNode.WhenAnyValue(n => n.Storage).Subscribe(UpdateDataModelPath).DisposeWith(d); - this.WhenAnyValue(vm => vm.DataModelPath).Subscribe(ApplyDataModelPath).DisposeWith(d); + this.WhenAnyValue(vm => vm.DataModelPath).WhereNotNull().Subscribe(ApplyDataModelPath).DisposeWith(d); Disposable.Create(() => { @@ -82,18 +82,18 @@ public class DataModelEventCycleNodeCustomViewModel : CustomNodeViewModel } } - private void ApplyDataModelPath(DataModelPath? path) + private void ApplyDataModelPath(DataModelPath path) { try { if (_updating) return; - if (path?.Path == _cycleNode.Storage?.Path) + if (path.Path == _cycleNode.Storage?.Path) return; _updating = true; - path?.Save(); + path.Save(); _nodeEditorService.ExecuteCommand(Script, new UpdateStorage(_cycleNode, path?.Entity, "event")); } finally diff --git a/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelEventNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelEventNodeCustomViewModel.cs index 9fe7d1058..3238ba287 100644 --- a/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelEventNodeCustomViewModel.cs +++ b/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelEventNodeCustomViewModel.cs @@ -44,7 +44,7 @@ public class DataModelEventNodeCustomViewModel : CustomNodeViewModel // Subscribe to node changes _node.WhenAnyValue(n => n.Storage).Subscribe(UpdateDataModelPath).DisposeWith(d); - this.WhenAnyValue(vm => vm.DataModelPath).Subscribe(ApplyDataModelPath).DisposeWith(d); + this.WhenAnyValue(vm => vm.DataModelPath).WhereNotNull().Subscribe(ApplyDataModelPath).DisposeWith(d); Disposable.Create(() => { @@ -89,18 +89,18 @@ public class DataModelEventNodeCustomViewModel : CustomNodeViewModel } } - private void ApplyDataModelPath(DataModelPath? path) + private void ApplyDataModelPath(DataModelPath path) { try { if (_updating) return; - if (path?.Path == _node.Storage?.Path) + if (path.Path == _node.Storage?.Path) return; _updating = true; - path?.Save(); + path.Save(); _nodeEditorService.ExecuteCommand(Script, new UpdateStorage(_node, path?.Entity, "event")); } finally diff --git a/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelNodeCustomViewModel.cs index 55b4d6dd2..91d502e98 100644 --- a/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelNodeCustomViewModel.cs +++ b/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelNodeCustomViewModel.cs @@ -41,7 +41,7 @@ public class DataModelNodeCustomViewModel : CustomNodeViewModel // Subscribe to node changes _node.WhenAnyValue(n => n.Storage).Subscribe(UpdateDataModelPath).DisposeWith(d); - this.WhenAnyValue(vm => vm.DataModelPath).Subscribe(ApplyDataModelPath).DisposeWith(d); + this.WhenAnyValue(vm => vm.DataModelPath).WhereNotNull().Subscribe(ApplyDataModelPath).DisposeWith(d); Disposable.Create(() => { @@ -86,18 +86,18 @@ public class DataModelNodeCustomViewModel : CustomNodeViewModel } } - private void ApplyDataModelPath(DataModelPath? path) + private void ApplyDataModelPath(DataModelPath path) { try { if (_updating) return; - if (path?.Path == _node.Storage?.Path) + if (path.Path == _node.Storage?.Path) return; _updating = true; - path?.Save(); + path.Save(); _nodeEditorService.ExecuteCommand(Script, new UpdateStorage(_node, path?.Entity, "path")); } finally