feat(attachments): drag-and-drop file attachments on the detail pane
Drop a file anywhere on the detail pane to attach it: pane-wide drop target with a 'Drop to attach' hover overlay (Copy cursor, gated on an idle selected task), an explicit lingering confirmation/error line, plus an Attachments list with size, remove, and an Add file… picker in the DETAILS card. ComposedPreview now shows the reference files too. en/de keys added.
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Reactive;
|
||||
using ClaudeDo.Ui.ViewModels.Islands;
|
||||
using ClaudeDo.Ui.Views.Modals;
|
||||
@@ -27,6 +29,78 @@ public partial class DetailsIslandView : UserControl
|
||||
// row Min/Max during a drag, so the console stops shrinking at 1/3.
|
||||
DetailBodyGrid.GetObservable(BoundsProperty)
|
||||
.Subscribe(new AnonymousObserver<Rect>(_ => UpdateRowLimits()));
|
||||
|
||||
AddHandler(DragDrop.DragEnterEvent, OnDragEnter);
|
||||
AddHandler(DragDrop.DragOverEvent, OnDragOver);
|
||||
AddHandler(DragDrop.DragLeaveEvent, OnDragLeave);
|
||||
AddHandler(DragDrop.DropEvent, OnDrop);
|
||||
}
|
||||
|
||||
private static bool IsFilesDrop(DragEventArgs e)
|
||||
=> e.DataTransfer?.Contains(DataFormat.File) == true;
|
||||
|
||||
private void OnDragEnter(object? sender, DragEventArgs e)
|
||||
{
|
||||
if (_vm is { CanAcceptDrop: true } && IsFilesDrop(e))
|
||||
{
|
||||
e.DragEffects = DragDropEffects.Copy;
|
||||
_vm.IsDragOver = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
e.DragEffects = DragDropEffects.None;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDragOver(object? sender, DragEventArgs e)
|
||||
{
|
||||
if (_vm is { CanAcceptDrop: true } && IsFilesDrop(e))
|
||||
{
|
||||
e.DragEffects = DragDropEffects.Copy;
|
||||
_vm.IsDragOver = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
e.DragEffects = DragDropEffects.None;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDragLeave(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_vm != null) _vm.IsDragOver = false;
|
||||
}
|
||||
|
||||
private async void OnDrop(object? sender, DragEventArgs e)
|
||||
{
|
||||
if (_vm != null) _vm.IsDragOver = false;
|
||||
if (_vm is not { CanAcceptDrop: true } || !IsFilesDrop(e)) return;
|
||||
e.Handled = true;
|
||||
|
||||
var items = e.DataTransfer.TryGetFiles();
|
||||
if (items is null) return;
|
||||
|
||||
var files = new List<(string FileName, System.IO.Stream Content)>();
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item is IStorageFile sf)
|
||||
{
|
||||
var stream = await sf.OpenReadAsync();
|
||||
files.Add((sf.Name, stream));
|
||||
}
|
||||
}
|
||||
|
||||
if (files.Count == 0) return;
|
||||
|
||||
try
|
||||
{
|
||||
await _vm.AddFilesAsync(files);
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var (_, s) in files) await s.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateRowLimits()
|
||||
|
||||
Reference in New Issue
Block a user