using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Screens.Scripting; using Artemis.UI.Screens.Sidebar; using Artemis.UI.Shared; using Artemis.UI.Shared.Routing; using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services.ProfileEditor; using Newtonsoft.Json; using ReactiveUI; namespace Artemis.UI.Screens.ProfileEditor.MenuBar; public class MenuBarViewModel : ActivatableViewModelBase { private readonly IRouter _router; private readonly IProfileEditorService _profileEditorService; private readonly IProfileService _profileService; private readonly ISettingsService _settingsService; private readonly IWindowService _windowService; private ObservableAsPropertyHelper? _focusFolder; private ObservableAsPropertyHelper? _focusNone; private ObservableAsPropertyHelper? _focusSelection; private ProfileEditorHistory? _history; private ObservableAsPropertyHelper? _isSuspended; private ObservableAsPropertyHelper? _keyBindingsEnabled; private ObservableAsPropertyHelper? _profileConfiguration; private ObservableAsPropertyHelper? _profileElement; private ObservableAsPropertyHelper? _suspendedEditing; public MenuBarViewModel(IRouter router, IProfileEditorService profileEditorService, IProfileService profileService, ISettingsService settingsService, IWindowService windowService) { _router = router; _profileEditorService = profileEditorService; _profileService = profileService; _settingsService = settingsService; _windowService = windowService; this.WhenActivated(d => { profileEditorService.History.Subscribe(history => History = history).DisposeWith(d); _profileConfiguration = profileEditorService.ProfileConfiguration.ToProperty(this, vm => vm.ProfileConfiguration).DisposeWith(d); _profileElement = profileEditorService.ProfileElement.ToProperty(this, vm => vm.ProfileElement).DisposeWith(d); _suspendedEditing = profileEditorService.SuspendedEditing.ToProperty(this, vm => vm.SuspendedEditing).DisposeWith(d); _isSuspended = profileEditorService.ProfileConfiguration .Select(p => p?.WhenAnyValue(c => c.IsSuspended) ?? Observable.Never()) .Switch() .ToProperty(this, vm => vm.IsSuspended) .DisposeWith(d); _focusNone = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.None).ToProperty(this, vm => vm.FocusNone).DisposeWith(d); _focusFolder = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Folder).ToProperty(this, vm => vm.FocusFolder).DisposeWith(d); _focusSelection = profileEditorService.FocusMode.Select(f => f == ProfileEditorFocusMode.Selection).ToProperty(this, vm => vm.FocusSelection).DisposeWith(d); _keyBindingsEnabled = Shared.UI.KeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d); }); AddFolder = ReactiveCommand.Create(ExecuteAddFolder); AddLayer = ReactiveCommand.Create(ExecuteAddLayer); ViewProperties = ReactiveCommand.CreateFromTask(ExecuteViewProperties, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); ViewScripts = ReactiveCommand.CreateFromTask(ExecuteViewScripts, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); AdaptProfile = ReactiveCommand.CreateFromTask(ExecuteAdaptProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); ToggleSuspended = ReactiveCommand.Create(ExecuteToggleSuspended, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); DeleteProfile = ReactiveCommand.CreateFromTask(ExecuteDeleteProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); ExportProfile = ReactiveCommand.CreateFromTask(ExecuteExportProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); DuplicateProfile = ReactiveCommand.CreateFromTask(ExecuteDuplicateProfile, this.WhenAnyValue(vm => vm.ProfileConfiguration).Select(c => c != null)); ToggleSuspendedEditing = ReactiveCommand.Create(ExecuteToggleSuspendedEditing); OpenUri = ReactiveCommand.Create(s => Process.Start(new ProcessStartInfo(s) {UseShellExecute = true, Verb = "open"})); ToggleBooleanSetting = ReactiveCommand.Create>(ExecuteToggleBooleanSetting); ChangeFocusMode = ReactiveCommand.Create(ExecuteChangeFocusMode); CycleFocusMode = ReactiveCommand.Create(ExecuteCycleFocusMode, this.WhenAnyValue(vm => vm.KeyBindingsEnabled)); } public ReactiveCommand AddFolder { get; } public ReactiveCommand AddLayer { get; } public ReactiveCommand ToggleSuspended { get; } public ReactiveCommand ViewProperties { get; } public ReactiveCommand ViewScripts { get; } public ReactiveCommand AdaptProfile { get; } public ReactiveCommand DeleteProfile { get; } public ReactiveCommand ExportProfile { get; } public ReactiveCommand DuplicateProfile { get; } public ReactiveCommand, Unit> ToggleBooleanSetting { get; } public ReactiveCommand OpenUri { get; } public ReactiveCommand ToggleSuspendedEditing { get; } public ReactiveCommand ChangeFocusMode { get; } public ReactiveCommand CycleFocusMode { get; } public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value; public RenderProfileElement? ProfileElement => _profileElement?.Value; public bool IsSuspended => _isSuspended?.Value ?? false; public bool SuspendedEditing => _suspendedEditing?.Value ?? false; public bool FocusNone => _focusNone?.Value ?? false; public bool FocusFolder => _focusFolder?.Value ?? false; public bool FocusSelection => _focusSelection?.Value ?? false; public bool KeyBindingsEnabled => _keyBindingsEnabled?.Value ?? false; public PluginSetting AutoSuspend => _settingsService.GetSetting("ProfileEditor.AutoSuspend", true); public PluginSetting ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); public PluginSetting ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false); public PluginSetting AlwaysShowValues => _settingsService.GetSetting("ProfileEditor.AlwaysShowValues", true); public PluginSetting AlwaysApplyDataBindings => _settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", false); public PluginSetting FocusMode => _settingsService.GetSetting("ProfileEditor.FocusMode", ProfileEditorFocusMode.Folder); public ProfileEditorHistory? History { get => _history; set => RaiseAndSetIfChanged(ref _history, value); } private void ExecuteAddFolder() { if (ProfileConfiguration?.Profile == null) return; RenderProfileElement target = ProfileElement ?? ProfileConfiguration.Profile.GetRootFolder(); _profileEditorService.CreateAndAddFolder(target); } private void ExecuteAddLayer() { if (ProfileConfiguration?.Profile == null) return; RenderProfileElement target = ProfileElement ?? ProfileConfiguration.Profile.GetRootFolder(); _profileEditorService.CreateAndAddLayer(target); } private async Task ExecuteViewProperties() { if (ProfileConfiguration == null) return; await _windowService.ShowDialogAsync(ProfileConfiguration.Category, ProfileConfiguration); } private async Task ExecuteViewScripts() { if (ProfileConfiguration?.Profile == null) return; await _windowService.ShowDialogAsync(ProfileConfiguration.Profile); await _profileEditorService.SaveProfileAsync(); } private async Task ExecuteAdaptProfile() { if (ProfileConfiguration?.Profile == null) return; bool confirmed = await _windowService.ShowConfirmContentDialog( "Adapt profile", "Are you sure you want to adapt the profile to your current surface? Layer assignments may change and you will lose your undo/redo history." ); if (!confirmed) return; _profileService.AdaptProfile(ProfileConfiguration.Profile); _history?.Clear(); } private void ExecuteToggleSuspended() { if (ProfileConfiguration == null) return; ProfileConfiguration.IsSuspended = !ProfileConfiguration.IsSuspended; _profileService.SaveProfileCategory(ProfileConfiguration.Category); } private async Task ExecuteDeleteProfile() { if (ProfileConfiguration == null) return; if (!await _windowService.ShowConfirmContentDialog("Delete profile", "Are you sure you want to permanently delete this profile?")) return; if (_profileService.FocusProfile == ProfileConfiguration) await _router.Navigate("home"); _profileService.RemoveProfileConfiguration(ProfileConfiguration); } private async Task ExecuteExportProfile() { if (ProfileConfiguration == null) return; // Might not cover everything but then the dialog will complain and that's good enough string fileName = Path.GetInvalidFileNameChars().Aggregate(ProfileConfiguration.Name, (current, c) => current.Replace(c, '-')); string? result = await _windowService.CreateSaveFileDialog() .HavingFilter(f => f.WithExtension("zip").WithName("Artemis profile")) .WithInitialFileName(fileName) .ShowAsync(); if (result == null) return; try { await using Stream stream = await _profileService.ExportProfile(ProfileConfiguration); await using FileStream fileStream = File.OpenWrite(result); await stream.CopyToAsync(fileStream); } catch (Exception e) { _windowService.ShowExceptionDialog("Failed to export profile", e); } } private async Task ExecuteDuplicateProfile() { if (ProfileConfiguration == null) return; await using Stream export = await _profileService.ExportProfile(ProfileConfiguration); await _profileService.ImportProfile(export, ProfileConfiguration.Category, true, false, "copy"); } private void ExecuteToggleSuspendedEditing() { _profileEditorService.ChangeSuspendedEditing(!SuspendedEditing); } private void ExecuteToggleBooleanSetting(PluginSetting setting) { setting.Value = !setting.Value; setting.Save(); } private void ExecuteCycleFocusMode() { if (FocusMode.Value == ProfileEditorFocusMode.None) FocusMode.Value = ProfileEditorFocusMode.Folder; else if (FocusMode.Value == ProfileEditorFocusMode.Folder) FocusMode.Value = ProfileEditorFocusMode.Selection; else if (FocusMode.Value == ProfileEditorFocusMode.Selection) FocusMode.Value = ProfileEditorFocusMode.None; FocusMode.Save(); _profileEditorService.ChangeFocusMode(FocusMode.Value); } private void ExecuteChangeFocusMode(ProfileEditorFocusMode focusMode) { FocusMode.Value = focusMode; FocusMode.Save(); _profileEditorService.ChangeFocusMode(FocusMode.Value); } }