Added pasting and sending features

This commit is contained in:
Ebbe Baß
2026-08-13 15:43:34 +02:00
parent 940e80b630
commit ceadaff995
22 changed files with 771 additions and 55 deletions
@@ -2,6 +2,7 @@ using System.Collections.ObjectModel;
using System.IO;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using EbbesMemeClipboard.Models;
using EbbesMemeClipboard.Services;
namespace EbbesMemeClipboard.ViewModels;
@@ -10,6 +11,8 @@ public partial class PickerViewModel : ObservableObject
{
private readonly ILocalMemeLibraryService _library;
private readonly IClipboardService _clipboard;
private readonly ISettingsService _settings;
private readonly IAutoPasteService _autoPaste;
public ObservableCollection<MemeTileViewModel> Items { get; } = new();
@@ -19,12 +22,26 @@ public partial class PickerViewModel : ObservableObject
[ObservableProperty]
private string _statusMessage = string.Empty;
/// <summary>
/// True while a modal dialog (currently: the "add memes" file picker) opened from within the
/// picker is showing. The window's Deactivated handler checks this so opening that dialog
/// doesn't get treated as "user clicked away" and hide the picker out from under it.
/// </summary>
[ObservableProperty]
private bool _isDialogOpen;
public event EventHandler? RequestClose;
public PickerViewModel(ILocalMemeLibraryService library, IClipboardService clipboard)
public PickerViewModel(
ILocalMemeLibraryService library,
IClipboardService clipboard,
ISettingsService settings,
IAutoPasteService autoPaste)
{
_library = library;
_clipboard = clipboard;
_settings = settings;
_autoPaste = autoPaste;
RefreshItems();
}
@@ -40,13 +57,32 @@ public partial class PickerViewModel : ObservableObject
await _clipboard.CopyLocalFileAsync(tile.FullPath);
StatusMessage = $"Copied {tile.DisplayName}";
RequestClose?.Invoke(this, EventArgs.Empty);
var mode = _settings.Current.InsertMode;
if (mode is InsertMode.PasteIntoActiveWindow or InsertMode.PasteAndSend)
{
await _autoPaste.PasteAsync(alsoSend: mode == InsertMode.PasteAndSend);
}
}
catch (IOException ex)
catch (Exception ex)
{
StatusMessage = $"Couldn't copy {tile.DisplayName}: {ex.Message}";
// Deliberately broad: CommunityToolkit's AsyncRelayCommand otherwise swallows any
// exception here silently (no crash, no message, the click just appears to do
// nothing), which makes clipboard/paste failures impossible to diagnose from the UI.
StatusMessage = $"Couldn't insert {tile.DisplayName}: {ex.Message}";
}
}
[RelayCommand]
private async Task RemoveItemAsync(MemeTileViewModel? tile)
{
if (tile is null) return;
await _library.RemoveAsync(tile.Record);
RefreshItems();
StatusMessage = $"Removed {tile.DisplayName}";
}
[RelayCommand]
private async Task ImportFilesAsync()
{
@@ -57,9 +93,17 @@ public partial class PickerViewModel : ObservableObject
Title = "Add memes",
};
if (dialog.ShowDialog() == true)
IsDialogOpen = true;
try
{
await ImportPathsAsync(dialog.FileNames);
if (dialog.ShowDialog() == true)
{
await ImportPathsAsync(dialog.FileNames);
}
}
finally
{
IsDialogOpen = false;
}
}
@@ -0,0 +1,76 @@
using System.Windows.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using EbbesMemeClipboard.Models;
using EbbesMemeClipboard.Services;
namespace EbbesMemeClipboard.ViewModels;
public partial class SettingsViewModel : ObservableObject
{
private readonly ISettingsService _settings;
private readonly IGlobalHotkeyService _hotkeyService;
[ObservableProperty]
private string _hotkeyDisplay = string.Empty;
[ObservableProperty]
private string _hotkeyError = string.Empty;
[ObservableProperty]
private InsertMode _insertMode;
public bool IsCopyOnly
{
get => InsertMode == InsertMode.CopyOnly;
set { if (value) InsertMode = InsertMode.CopyOnly; }
}
public bool IsPasteIntoActiveWindow
{
get => InsertMode == InsertMode.PasteIntoActiveWindow;
set { if (value) InsertMode = InsertMode.PasteIntoActiveWindow; }
}
public bool IsPasteAndSend
{
get => InsertMode == InsertMode.PasteAndSend;
set { if (value) InsertMode = InsertMode.PasteAndSend; }
}
public SettingsViewModel(ISettingsService settings, IGlobalHotkeyService hotkeyService)
{
_settings = settings;
_hotkeyService = hotkeyService;
_insertMode = _settings.Current.InsertMode;
_hotkeyDisplay = HotkeyFormatter.Format(_settings.Current.HotkeyModifiers, _settings.Current.HotkeyKey);
}
partial void OnInsertModeChanged(InsertMode value)
{
_settings.Current.InsertMode = value;
_ = _settings.SaveAsync();
OnPropertyChanged(nameof(IsCopyOnly));
OnPropertyChanged(nameof(IsPasteIntoActiveWindow));
OnPropertyChanged(nameof(IsPasteAndSend));
}
public void TrySetHotkey(ModifierKeys modifiers, Key key)
{
if (_hotkeyService.Register(modifiers, key))
{
_settings.Current.HotkeyModifiers = modifiers;
_settings.Current.HotkeyKey = key;
_ = _settings.SaveAsync();
HotkeyDisplay = HotkeyFormatter.Format(modifiers, key);
HotkeyError = string.Empty;
}
else
{
// Register() unregisters the old combo before trying the new one, so on failure
// nothing is registered at all - restore the last-known-working combo rather than
// leaving the app with no working hotkey.
_hotkeyService.Register(_settings.Current.HotkeyModifiers, _settings.Current.HotkeyKey);
HotkeyError = "That shortcut is already in use - try a different one.";
}
}
}