Skip to content

Commit

Permalink
fixed save disabled message keys, added OokiiMessageService, implemen…
Browse files Browse the repository at this point in the history
…ted OnDocumentTabClosed (TODO save+notify)
  • Loading branch information
retailcoder committed Mar 16, 2024
1 parent f9c3ec4 commit c7ea5f0
Show file tree
Hide file tree
Showing 18 changed files with 706 additions and 119 deletions.
2 changes: 1 addition & 1 deletion Client/Rubberduck.Editor/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ private void ConfigureServices(IServiceCollection services)

services.AddSingleton<MessageActionsProvider>();
services.AddSingleton<IMessageWindowFactory, MessageWindowFactory>();
services.AddSingleton<IMessageService, MessageService>();
services.AddSingleton<IMessageService, OokiiMessageService>();
services.AddSingleton<ShowMessageHandler>();
services.AddSingleton<ShowMessageRequestHandler>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public override async Task<MessageActionItem> Handle(ShowMessageRequestParams re
if (actions is null)
{
_service.LogWarning("ShowMessageRequestParams did not include any message actions; using default Accept/Cancel actions. This is likely a bug.");
actions = new[] { MessageAction.AcceptAction, MessageAction.CancelAction };
actions = [MessageAction.AcceptAction, MessageAction.CancelAction];
}

var model = MessageRequestModel.For(level, request.Message, actions);
Expand Down
34 changes: 26 additions & 8 deletions Client/Rubberduck.Editor/Shell/ShellWindowViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using Dragablz;
using Microsoft.Extensions.Logging;
using Rubberduck.Editor.Commands;
using Rubberduck.InternalApi.Settings.Model.Editor.Tools;
using Rubberduck.UI;
using Rubberduck.UI.Chrome;
using Rubberduck.UI.Command;
using Rubberduck.UI.Services;
using Rubberduck.UI.Shared.Message;
using Rubberduck.UI.Shell;
using Rubberduck.UI.Shell.Document;
using Rubberduck.UI.Shell.StatusBar;
Expand All @@ -15,15 +17,14 @@
using System.Collections.Specialized;
using System.Linq;
using System.Windows.Input;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;

namespace Rubberduck.Editor.Shell
{
public class ShellWindowViewModel : ViewModelBase, IShellWindowViewModel
{
private readonly UIServiceHelper _service;
private readonly IMessageService _service;

public ShellWindowViewModel(UIServiceHelper service,
public ShellWindowViewModel(IMessageService service,
InterTabClient interTabClient,
InterToolTabClient interToolTabClient,
IShellStatusBarViewModel statusBar,
Expand Down Expand Up @@ -156,27 +157,44 @@ public IDocumentTabViewModel ActiveDocumentTab
public IInterTabClient InterTabClient { get; init; }
public IInterTabClient InterToolTabClient { get; init; }

public ItemActionCallback ClosingTabItemHandler => OnTabClosed;
public ItemActionCallback ClosingTabItemHandler => OnDocumentTabClosed;

public IToolPanelViewModel LeftToolPanel { get; }

public IToolPanelViewModel RightToolPanel { get; }

public IToolPanelViewModel BottomToolPanel { get; }

private void OnTabClosed(ItemActionCallbackArgs<TabablzControl> args)
private static MessageAction _discardChangesAction = new("UnsavedChanges_Discard", "UnsavedChanges_DiscardTooltip");
private static MessageAction _saveAndCloseAction = new("UnsavedChanges_SaveAndClose", "UnsavedChanges_SaveAndCloseTooltip", isDefaultAction: true);
private static MessageAction _leaveOpenAction = new("UnsavedChanges_LeaveOpen", "UnsavedChanges_LeaveOpenTooltip");

private void OnDocumentTabClosed(ItemActionCallbackArgs<TabablzControl> args)
{
if (args.DragablzItem.DataContext is IDocumentTabViewModel tab)
{
var uri = tab.DocumentUri;
if (tab.DocumentState.IsModified)
{
/* TODO prompt to save changes, offer to cancel, etc.*/
var verbose = $"DocumentId: {uri}";
var prompt = _service.ShowMessageRequest(MessageRequestModel.For(
level: LogLevel.Warning,
key: nameof(OnDocumentTabClosed),
verbose: verbose,
actions: [_discardChangesAction, _saveAndCloseAction, _leaveOpenAction]));

if (!prompt.IsEnabled || prompt.MessageAction == _saveAndCloseAction)
{
// TODO write to file system, notify server
}
else if (prompt.MessageAction == _leaveOpenAction || prompt.MessageAction == MessageAction.CancelAction)
{
args.Cancel();
return;
}
}
FileCommands.CloseActiveDocumentCommand.Execute(uri, args.DragablzItem);
}

//args.Cancel();
}
}

Expand Down
36 changes: 36 additions & 0 deletions Client/Rubberduck.UI/Shared/Message/IMessageService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Microsoft.Extensions.Logging;
using Rubberduck.UI.Command.SharedHandlers;
using System;

namespace Rubberduck.UI.Shared.Message
{
public interface IMessageService
{
/// <summary>
/// Displays a message to the user, requesting an action.
/// </summary>
/// <returns>
/// <c>MessageActionResult.Disabled</c> if the model key is disabled.
/// </returns>
MessageActionResult ShowMessageRequest(MessageRequestModel model, Func<MessageActionsProvider, MessageActionCommand[]>? actions = null);

/// <summary>
/// Displays a message to the user.
/// </summary>
/// <returns>
/// <c>MessageActionResult.Disabled</c> if the model key is disabled.
/// </returns>
MessageActionResult ShowMessage(MessageModel model, Func<MessageActionsProvider, MessageActionCommand[]>? actions = null);

/// <summary>
/// Displays user-facing exception error message, including the stack trace.
/// </summary>
/// <remarks>
/// If the specified key does not exist in <c>RubberduckUI</c> resource strings, the <c>LogLevel</c> is used as a title.
/// </remarks>
/// <param name="key">The resource key for the message. Used for tracking whether or not this message should be shown.</param>
/// <param name="exception">The exception to display details about.</param>
/// <param name="level">Specify a 'level' that the implementation may use to display a corresponding icon.</param>
void ShowError(string key, Exception exception, LogLevel level = LogLevel.Error);
}
}
19 changes: 16 additions & 3 deletions Client/Rubberduck.UI/Shared/Message/MessageModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,29 @@ public static class MessageKeys

public class MessageRequestModel : MessageModel
{
public static MessageRequestModel For(LogLevel level, string key, string verbose, MessageAction[] actions)
{
return new()
{
Key = key,
Title = RubberduckUI.Rubberduck,
Message = Resources.v3.RubberduckMessages.ResourceManager.GetString(key) ?? $"[missing key:{key}]",
Verbose = verbose,
Level = level,

MessageActions = actions,
};
}

public static MessageRequestModel For(LogLevel level, string message, MessageAction[] actions)
{
var model = FromShowMessageParams(message, level);
return new()
{
Key = model.Key,
Title = model.Title,
Message = model.Message,
Verbose = model.Verbose,
Level = model.Level,
Message = message,
Level = level,

MessageActions = actions,
};
Expand Down
177 changes: 73 additions & 104 deletions Client/Rubberduck.UI/Shared/Message/MessageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,138 +7,107 @@
using System;
using System.Linq;

namespace Rubberduck.UI.Shared.Message
namespace Rubberduck.UI.Shared.Message;

public class MessageService : UIServiceHelper, IMessageService
{
public interface IMessageService
private readonly IMessageWindowFactory _viewFactory;

public MessageService(RubberduckSettingsProvider settings, ILogger<MessageService> logger,
IMessageWindowFactory viewFactory,
PerformanceRecordAggregator performance)
: base(logger, settings, performance)
{
/// <summary>
/// Displays a message to the user, requesting an action.
/// </summary>
/// <returns>
/// <c>MessageActionResult.Disabled</c> if the model key is disabled.
/// </returns>
MessageActionResult ShowMessageRequest(MessageRequestModel model, Func<MessageActionsProvider, MessageActionCommand[]>? actions = null);

/// <summary>
/// Displays a message to the user.
/// </summary>
/// <returns>
/// <c>MessageActionResult.Disabled</c> if the model key is disabled.
/// </returns>
MessageActionResult ShowMessage(MessageModel model, Func<MessageActionsProvider, MessageActionCommand[]>? actions = null);

/// <summary>
/// Displays user-facing exception error message, including the stack trace.
/// </summary>
/// <remarks>
/// If the specified key does not exist in <c>RubberduckUI</c> resource strings, the <c>LogLevel</c> is used as a title.
/// </remarks>
/// <param name="key">The resource key for the message. Used for tracking whether or not this message should be shown.</param>
/// <param name="exception">The exception to display details about.</param>
/// <param name="level">Specify a 'level' that the implementation may use to display a corresponding icon.</param>
void ShowError(string key, Exception exception, LogLevel level = LogLevel.Error);
_viewFactory = viewFactory;
}

public class MessageService : UIServiceHelper, IMessageService
protected override void OnUserFacingException(Exception exception, string? message)
{
private readonly IMessageWindowFactory _viewFactory;

public MessageService(RubberduckSettingsProvider settings, ILogger<MessageService> logger,
IMessageWindowFactory viewFactory,
PerformanceRecordAggregator performance)
: base(logger, settings, performance)
{
_viewFactory = viewFactory;
}
ShowError(exception.TargetSite?.Name ?? exception.GetType().Name, exception);
}

protected override void OnUserFacingException(Exception exception, string? message)
public MessageActionResult ShowMessageRequest(MessageRequestModel model, Func<MessageActionsProvider, MessageActionCommand[]>? actions = null)
{
if (CanShowMessageKey(model.Key))
{
ShowError(exception.TargetSite?.Name ?? exception.GetType().Name, exception);
}
static MessageActionCommand[] defaultActions(MessageActionsProvider provider) => provider.OkCancel();
MessageAction? selection = null;
IMessageWindowViewModel? viewModel = null;

public MessageActionResult ShowMessageRequest(MessageRequestModel model, Func<MessageActionsProvider, MessageActionCommand[]>? actions = null)
{
if (CanShowMessageKey(model.Key))
RunOnMainThread(() =>
{
static MessageActionCommand[] defaultActions(MessageActionsProvider provider) => provider.OkCancel();
MessageAction? selection = null;
IMessageWindowViewModel? viewModel = null;

RunOnMainThread(() =>
{
var (view, viewModel) = _viewFactory.Create(model, actions ?? defaultActions);
view.ShowDialog();

selection = viewModel.SelectedAction;
});
var (view, viewModel) = _viewFactory.Create(model, actions ?? defaultActions);
view.ShowDialog();

viewModel = viewModel ?? throw new InvalidOperationException();
selection = viewModel.SelectedAction;
});

if (selection is not null)
{
LogTrace("User has closed the message window.", $"Selected action: {selection.ResourceKey}");
return new MessageActionResult { MessageAction = selection, IsEnabled = viewModel.IsEnabled };
}
viewModel = viewModel ?? throw new InvalidOperationException();

LogWarning("User has closed the message window, but no message action was set. This is likely a bug.");
return viewModel.IsEnabled ? MessageActionResult.Default : MessageActionResult.Disabled;
}
else
if (selection is not null)
{
LogTrace("Message key was disabled by the user; message will not be shown.", $"Key: {model.Key} ({model.Level})");
LogTrace("User has closed the message window.", $"Selected action: {selection.ResourceKey}");
return new MessageActionResult { MessageAction = selection, IsEnabled = viewModel.IsEnabled };
}

return MessageActionResult.Disabled;
LogWarning("User has closed the message window, but no message action was set. This is likely a bug.");
return viewModel.IsEnabled ? MessageActionResult.Default : MessageActionResult.Disabled;
}

public MessageActionResult ShowMessage(MessageModel model, Func<MessageActionsProvider, MessageActionCommand[]>? actions = null)
else
{
if (CanShowMessageKey(model.Key))
{
MessageAction? selection = null;
MessageWindow? view = null;
IMessageWindowViewModel? viewModel = null;
LogTrace("Message key was disabled by the user; message will not be shown.", $"Key: {model.Key} ({model.Level})");
}

RunOnMainThread(() =>
{
(view, viewModel) = _viewFactory.Create(model, actions);
view.ShowDialog();
return MessageActionResult.Disabled;
}

selection = viewModel.SelectedAction;
});
public MessageActionResult ShowMessage(MessageModel model, Func<MessageActionsProvider, MessageActionCommand[]>? actions = null)
{
if (CanShowMessageKey(model.Key))
{
MessageAction? selection = null;
MessageWindow? view = null;
IMessageWindowViewModel? viewModel = null;

viewModel = viewModel ?? throw new InvalidOperationException();
RunOnMainThread(() =>
{
(view, viewModel) = _viewFactory.Create(model, actions);
view.ShowDialog();

if (selection is not null)
{
LogTrace("User has closed the message window.", $"Selected action: {selection.ResourceKey}");
return new MessageActionResult { MessageAction = selection, IsEnabled = viewModel.IsEnabled };
}
selection = viewModel.SelectedAction;
});

LogWarning("User has closed the message window, but no message action was set. This is likely a bug.");
return viewModel.IsEnabled ? MessageActionResult.Default : MessageActionResult.Disabled;
}
else
viewModel = viewModel ?? throw new InvalidOperationException();

if (selection is not null)
{
LogTrace("Message key was disabled by the user; message will not be shown.", $"Key: {model.Key} ({model.Level})");
LogTrace("User has closed the message window.", $"Selected action: {selection.ResourceKey}");
return new MessageActionResult { MessageAction = selection, IsEnabled = viewModel.IsEnabled };
}

return MessageActionResult.Disabled;
LogWarning("User has closed the message window, but no message action was set. This is likely a bug.");
return viewModel.IsEnabled ? MessageActionResult.Default : MessageActionResult.Disabled;
}

public void ShowError(string key, Exception exception, LogLevel level = LogLevel.Error)
else
{
var model = new MessageModel
{
Key = key,
Level = level,
Message = exception.Message,
Verbose = exception.ToString(), // TODO make a markdown formatter for exception details
Title = RubberduckUI.ResourceManager.GetString(key) ?? level.ToString()
};
ShowMessage(model, provider => provider.OkOnly());
LogTrace("Message key was disabled by the user; message will not be shown.", $"Key: {model.Key} ({model.Level})");
}

private bool CanShowMessageKey(string key) => !Settings.GeneralSettings.DisabledMessageKeys.Contains(key);
return MessageActionResult.Disabled;
}

public void ShowError(string key, Exception exception, LogLevel level = LogLevel.Error)
{
var model = new MessageModel
{
Key = key,
Level = level,
Message = exception.Message,
Verbose = exception.ToString(), // TODO make a markdown formatter for exception details
Title = RubberduckUI.ResourceManager.GetString(key) ?? level.ToString()
};
ShowMessage(model, provider => provider.OkOnly());
}

private bool CanShowMessageKey(string key) => !Settings.GeneralSettings.DisabledMessageKeys.Contains(key);
}
Loading

0 comments on commit c7ea5f0

Please sign in to comment.