feat(ui): drag a task into Mission Control to queue it
This commit is contained in:
@@ -95,6 +95,26 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
|||||||
catch { /* best-effort queue refresh */ }
|
catch { /* best-effort queue refresh */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drop-to-queue: a task dragged from the main app onto Mission Control gets queued.
|
||||||
|
public async System.Threading.Tasks.Task EnqueueTaskAsync(string taskId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(taskId)) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == taskId);
|
||||||
|
if (entity is null
|
||||||
|
|| entity.Status == ClaudeDo.Data.Models.TaskStatus.Running
|
||||||
|
|| entity.Status == ClaudeDo.Data.Models.TaskStatus.Queued)
|
||||||
|
return;
|
||||||
|
entity.Status = ClaudeDo.Data.Models.TaskStatus.Queued;
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
await _worker.WakeQueueAsync();
|
||||||
|
}
|
||||||
|
catch { /* best-effort enqueue */ }
|
||||||
|
await RefreshQueueAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private void SeedActive()
|
private void SeedActive()
|
||||||
{
|
{
|
||||||
foreach (var a in _worker.GetActiveTasks())
|
foreach (var a in _worker.GetActiveTasks())
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ namespace ClaudeDo.Ui.Views.Islands;
|
|||||||
|
|
||||||
public partial class TasksIslandView : UserControl
|
public partial class TasksIslandView : UserControl
|
||||||
{
|
{
|
||||||
private static readonly DataFormat<string> TaskRowFormat =
|
// Public so the Mission Control window can accept the same drag payload (drop-to-queue).
|
||||||
|
public static readonly DataFormat<string> TaskRowFormat =
|
||||||
DataFormat.CreateStringApplicationFormat("claudedo-task-row");
|
DataFormat.CreateStringApplicationFormat("claudedo-task-row");
|
||||||
|
|
||||||
public TasksIslandView()
|
public TasksIslandView()
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
xmlns:loc="using:ClaudeDo.Ui.Localization"
|
xmlns:loc="using:ClaudeDo.Ui.Localization"
|
||||||
x:DataType="vm:MissionControlViewModel"
|
x:DataType="vm:MissionControlViewModel"
|
||||||
x:Class="ClaudeDo.Ui.Views.MissionControl.MissionControlView">
|
x:Class="ClaudeDo.Ui.Views.MissionControl.MissionControlView">
|
||||||
<DockPanel LastChildFill="True" Background="{DynamicResource VoidBrush}">
|
<DockPanel LastChildFill="True" Background="{DynamicResource VoidBrush}"
|
||||||
|
DragDrop.AllowDrop="True">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<Border DockPanel.Dock="Top"
|
<Border DockPanel.Dock="Top"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Avalonia.Interactivity;
|
|||||||
using Avalonia.VisualTree;
|
using Avalonia.VisualTree;
|
||||||
using ClaudeDo.Ui.ViewModels;
|
using ClaudeDo.Ui.ViewModels;
|
||||||
using ClaudeDo.Ui.ViewModels.Islands;
|
using ClaudeDo.Ui.ViewModels.Islands;
|
||||||
|
using ClaudeDo.Ui.Views.Islands;
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.Views.MissionControl;
|
namespace ClaudeDo.Ui.Views.MissionControl;
|
||||||
|
|
||||||
@@ -23,15 +24,28 @@ public partial class MissionControlView : UserControl
|
|||||||
|
|
||||||
private void OnPaneDragOver(object? sender, DragEventArgs e)
|
private void OnPaneDragOver(object? sender, DragEventArgs e)
|
||||||
{
|
{
|
||||||
e.DragEffects = (e.DataTransfer?.Contains(PaneFormat) ?? false)
|
var dt = e.DataTransfer;
|
||||||
? DragDropEffects.Move
|
var accept = (dt?.Contains(PaneFormat) ?? false)
|
||||||
: DragDropEffects.None;
|
|| (dt?.Contains(TasksIslandView.TaskRowFormat) ?? false);
|
||||||
|
e.DragEffects = accept ? DragDropEffects.Move : DragDropEffects.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPaneDrop(object? sender, DragEventArgs e)
|
private void OnPaneDrop(object? sender, DragEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is not MissionControlViewModel vm) return;
|
if (DataContext is not MissionControlViewModel vm) return;
|
||||||
var draggedId = e.DataTransfer?.TryGetValue(PaneFormat);
|
var dt = e.DataTransfer;
|
||||||
|
if (dt is null) return;
|
||||||
|
|
||||||
|
// A task dragged from the main app → queue it.
|
||||||
|
var taskId = dt.TryGetValue(TasksIslandView.TaskRowFormat);
|
||||||
|
if (!string.IsNullOrEmpty(taskId))
|
||||||
|
{
|
||||||
|
_ = vm.EnqueueTaskAsync(taskId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A pane dragged within the grid → reorder.
|
||||||
|
var draggedId = dt.TryGetValue(PaneFormat);
|
||||||
if (string.IsNullOrEmpty(draggedId)) return;
|
if (string.IsNullOrEmpty(draggedId)) return;
|
||||||
if (e.Source is not Avalonia.Visual src) return;
|
if (e.Source is not Avalonia.Visual src) return;
|
||||||
|
|
||||||
|
|||||||
@@ -239,4 +239,27 @@ public class MissionControlViewModelTests : IDisposable
|
|||||||
db.Tasks.Add(new TaskEntity { Id = "idle1", ListId = "L1", Title = "idle", Status = TaskStatus.Idle, CreatedAt = DateTime.UtcNow, SortOrder = 2 });
|
db.Tasks.Add(new TaskEntity { Id = "idle1", ListId = "L1", Title = "idle", Status = TaskStatus.Idle, CreatedAt = DateTime.UtcNow, SortOrder = 2 });
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EnqueueTaskAsync_SetsTaskQueued_AndShowsInStrip()
|
||||||
|
{
|
||||||
|
await using (var db = NewContext())
|
||||||
|
{
|
||||||
|
db.Lists.Add(new ListEntity { Id = "L1", Name = "Work", CreatedAt = DateTime.UtcNow });
|
||||||
|
db.Tasks.Add(new TaskEntity { Id = "idleTask", ListId = "L1", Title = "Do the thing", Status = TaskStatus.Idle, CreatedAt = DateTime.UtcNow, SortOrder = 0 });
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
var worker = new FakeWorker();
|
||||||
|
using var vm = BuildVm(worker);
|
||||||
|
|
||||||
|
await vm.EnqueueTaskAsync("idleTask");
|
||||||
|
|
||||||
|
Assert.True(vm.HasQueued);
|
||||||
|
Assert.Contains(vm.Queued, q => q.Id == "idleTask");
|
||||||
|
|
||||||
|
await using var verify = NewContext();
|
||||||
|
var entity = await verify.Tasks.FirstAsync(t => t.Id == "idleTask");
|
||||||
|
Assert.Equal(TaskStatus.Queued, entity.Status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user