diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs index 368f3961c..79b6d7a59 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading.Tasks; @@ -64,6 +65,19 @@ internal class ProfileEditorService : IProfileEditorService PixelsPerSecond = _pixelsPerSecondSubject.AsObservable(); Tools = tools; SelectedKeyframes = selectedKeyframes; + + // Observe executing, undoing and redoing commands and run the auto-save after 1 second + History.Select(h => h?.Execute ?? Observable.Never()) + .Switch() + .Merge(History.Select(h => h?.Undo ?? Observable.Never()) + .Switch() + .Select(_ => Unit.Default)) + .Merge(History.Select(h => h?.Redo ?? Observable.Never()) + .Switch() + .Select(_ => Unit.Default)) + .Throttle(TimeSpan.FromSeconds(1)) + .SelectMany(async _ => await AutoSaveProfileAsync()) + .Subscribe(); } public IObservable ProfileConfiguration { get; } @@ -425,6 +439,22 @@ internal class ProfileEditorService : IProfileEditorService return newHistory; } + private async Task AutoSaveProfileAsync() + { + try + { + await SaveProfileAsync(); + } + catch (Exception e) + { + _windowService.ShowExceptionDialog("Failed to auto-save profile", e); + _logger.Error(e, "Failed to auto-save profile"); + throw; + } + + return Unit.Default; + } + private void Tick(TimeSpan time) { if (_profileConfigurationSubject.Value?.Profile == null || _suspendedEditingSubject.Value) diff --git a/src/Artemis.UI.Windows/App.axaml.cs b/src/Artemis.UI.Windows/App.axaml.cs index 6d5aa72e1..94834a5ac 100644 --- a/src/Artemis.UI.Windows/App.axaml.cs +++ b/src/Artemis.UI.Windows/App.axaml.cs @@ -22,13 +22,12 @@ namespace Artemis.UI.Windows public override void OnFrameworkInitializationCompleted() { + if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) + return; + ArtemisBootstrapper.Initialize(); - - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - _applicationStateManager = new ApplicationStateManager(_kernel!, desktop.Args); - RegisterProviders(_kernel!); - } + _applicationStateManager = new ApplicationStateManager(_kernel!, desktop.Args); + RegisterProviders(_kernel!); } private void RegisterProviders(StandardKernel standardKernel) diff --git a/src/Artemis.UI/MainWindow.axaml b/src/Artemis.UI/MainWindow.axaml index 5228ca673..da1cbb348 100644 --- a/src/Artemis.UI/MainWindow.axaml +++ b/src/Artemis.UI/MainWindow.axaml @@ -8,6 +8,7 @@ Icon="/Assets/Images/Logo/application.ico" Title="Artemis 2.0"> + @@ -16,7 +17,7 @@ - + diff --git a/src/Artemis.UI/MainWindow.axaml.cs b/src/Artemis.UI/MainWindow.axaml.cs index de5ee0368..77be8f698 100644 --- a/src/Artemis.UI/MainWindow.axaml.cs +++ b/src/Artemis.UI/MainWindow.axaml.cs @@ -41,7 +41,7 @@ namespace Artemis.UI if (coreAppTitleBar.TitleBar != null) { coreAppTitleBar.TitleBar.ExtendViewIntoTitleBar = true; - SetTitleBar(this.Get("TitleBar")); + SetTitleBar(this.Get("DragHandle")); } } diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs index dc5b5767d..4872f92bb 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs @@ -132,6 +132,7 @@ public class MenuBarViewModel : ActivatableViewModelBase return; await _windowService.ShowDialogAsync(("profile", ProfileConfiguration.Profile)); + await _profileEditorService.SaveProfileAsync(); } private async Task ExecuteAdaptProfile() diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackView.axaml index b092ca595..615b1189c 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackView.axaml @@ -34,7 +34,7 @@ diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs index a711d59c1..56aee2362 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Playback/PlaybackViewModel.cs @@ -28,7 +28,8 @@ public class PlaybackViewModel : ActivatableViewModelBase { _profileEditorService = profileEditorService; _settingsService = settingsService; - + _repeatTimeline = true; + this.WhenActivated(d => { _profileEditorService.ProfileElement.Subscribe(e => _profileElement = e).DisposeWith(d); @@ -142,6 +143,7 @@ public class PlaybackViewModel : ActivatableViewModelBase { RepeatTimeline = false; RepeatSegment = true; + this.RaisePropertyChanged(nameof(Repeating)); } else if (RepeatSegment) { @@ -149,6 +151,7 @@ public class PlaybackViewModel : ActivatableViewModelBase RepeatSegment = false; Repeating = false; } + } private TimeSpan GetCurrentSegmentStart() diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowView.axaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowView.axaml.cs index 3016b7d79..8cd56319a 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowView.axaml.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Windows/BrushConfigurationWindowView.axaml.cs @@ -6,6 +6,8 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Windows; public class BrushConfigurationWindowView : ReactiveCoreWindow { + private bool _canClose; + public BrushConfigurationWindowView() { InitializeComponent(); @@ -22,9 +24,14 @@ public class BrushConfigurationWindowView : ReactiveCoreWindow { + private bool _canClose; + public EffectConfigurationWindowView() { InitializeComponent(); @@ -22,9 +24,14 @@ public class EffectConfigurationWindowView : ReactiveCoreWindow + Title="Artemis | Scripts" + Width="1200" + Height="750"> - + @@ -29,7 +31,8 @@ Grid.Column="1" VerticalAlignment="Center" Text="{CompiledBinding ScriptConfiguration.Name}" - IsVisible="{CompiledBinding !ScriptConfiguration.HasChanges}" /> + IsVisible="{CompiledBinding !ScriptConfiguration.HasChanges}" + TextTrimming="CharacterEllipsis" /> - + diff --git a/src/Artemis.UI/Screens/Scripting/ScriptsDialogView.axaml.cs b/src/Artemis.UI/Screens/Scripting/ScriptsDialogView.axaml.cs index ea1832caf..ca4a396f7 100644 --- a/src/Artemis.UI/Screens/Scripting/ScriptsDialogView.axaml.cs +++ b/src/Artemis.UI/Screens/Scripting/ScriptsDialogView.axaml.cs @@ -1,4 +1,5 @@ -using Avalonia; +using System.ComponentModel; +using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; @@ -6,12 +7,28 @@ namespace Artemis.UI.Screens.Scripting; public partial class ScriptsDialogView : ReactiveCoreWindow { + private bool _canClose; + public ScriptsDialogView() { InitializeComponent(); #if DEBUG this.AttachDevTools(); #endif + Closing += OnClosing; + } + + private async void OnClosing(object? sender, CancelEventArgs e) + { + if (_canClose) + return; + + e.Cancel = true; + if (ViewModel == null || await ViewModel.CanClose()) + { + _canClose = true; + Close(); + } } private void InitializeComponent() diff --git a/src/Artemis.UI/Screens/Scripting/ScriptsDialogViewModel.cs b/src/Artemis.UI/Screens/Scripting/ScriptsDialogViewModel.cs index abc7db1ad..8aa44521e 100644 --- a/src/Artemis.UI/Screens/Scripting/ScriptsDialogViewModel.cs +++ b/src/Artemis.UI/Screens/Scripting/ScriptsDialogViewModel.cs @@ -143,4 +143,18 @@ public class ScriptsDialogViewModel : DialogViewModelBase // Select the new script SelectedScript = ScriptConfigurations.LastOrDefault(); } + + public async Task CanClose() + { + if (!ScriptConfigurations.Any(s => s.ScriptConfiguration.HasChanges)) + return true; + + bool result = await _windowService.ShowConfirmContentDialog("Discard changes", "One or more scripts still have pending changes, do you want to discard them?"); + if (!result) + return false; + + foreach (ScriptConfigurationViewModel scriptConfigurationViewModel in ScriptConfigurations) + scriptConfigurationViewModel.ScriptConfiguration.DiscardPendingChanges(); + return true; + } } \ No newline at end of file diff --git a/src/Artemis.UI/ViewLocator.cs b/src/Artemis.UI/ViewLocator.cs index 5205c004a..45ceec2d2 100644 --- a/src/Artemis.UI/ViewLocator.cs +++ b/src/Artemis.UI/ViewLocator.cs @@ -23,11 +23,7 @@ public class ViewLocator : IDataTemplate throw new ArtemisUIException($"The views of activatable view models should inherit ReactiveUserControl, in this case ReactiveUserControl<{data.GetType().Name}>."); if (type != null) - { - Debug.WriteLine("[ViewLocator] Creating instance of '{0}'", type); return (Control) Activator.CreateInstance(type)!; - } - return new TextBlock {Text = "Not Found: " + name}; }