feat(ui): TasksIslandViewModel with smart/virtual/user filtering
Uses IDbContextFactory directly (singleton-safe). Adds ToggleDone, ToggleStar, Select commands. Fixes pre-existing SessionTerminalView compiled-binding error on Classes property via x:CompileBindings="False". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -103,8 +103,12 @@ sealed class Program
|
|||||||
|
|
||||||
// Islands shell VMs
|
// Islands shell VMs
|
||||||
sc.AddSingleton<ListsIslandViewModel>();
|
sc.AddSingleton<ListsIslandViewModel>();
|
||||||
sc.AddSingleton<TasksIslandViewModel>();
|
sc.AddSingleton<TasksIslandViewModel>(sp =>
|
||||||
sc.AddSingleton<DetailsIslandViewModel>();
|
new TasksIslandViewModel(sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>()));
|
||||||
|
sc.AddSingleton<DetailsIslandViewModel>(sp =>
|
||||||
|
new DetailsIslandViewModel(
|
||||||
|
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>(),
|
||||||
|
sp.GetRequiredService<WorkerClient>()));
|
||||||
sc.AddSingleton<IslandsShellViewModel>();
|
sc.AddSingleton<IslandsShellViewModel>();
|
||||||
|
|
||||||
return sc.BuildServiceProvider();
|
return sc.BuildServiceProvider();
|
||||||
|
|||||||
@@ -1,12 +1,127 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using ClaudeDo.Data;
|
||||||
|
using ClaudeDo.Data.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.ViewModels.Islands;
|
namespace ClaudeDo.Ui.ViewModels.Islands;
|
||||||
|
|
||||||
public sealed partial class TasksIslandViewModel : ViewModelBase
|
public sealed partial class TasksIslandViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
|
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||||
|
private ListNavItemViewModel? _currentList;
|
||||||
|
|
||||||
public event EventHandler? SelectionChanged;
|
public event EventHandler? SelectionChanged;
|
||||||
|
|
||||||
|
public ObservableCollection<TaskRowViewModel> Items { get; } = new();
|
||||||
|
|
||||||
|
[ObservableProperty] private string _newTaskTitle = "";
|
||||||
[ObservableProperty] private TaskRowViewModel? _selectedTask;
|
[ObservableProperty] private TaskRowViewModel? _selectedTask;
|
||||||
public void LoadForList(ListNavItemViewModel? list) { /* Phase 5 */ }
|
[ObservableProperty] private string _headerTitle = "";
|
||||||
partial void OnSelectedTaskChanged(TaskRowViewModel? value) =>
|
[ObservableProperty] private string _headerEyebrow = "";
|
||||||
|
[ObservableProperty] private string _subtitle = "";
|
||||||
|
|
||||||
|
public TasksIslandViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory)
|
||||||
|
{
|
||||||
|
_dbFactory = dbFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void LoadForList(ListNavItemViewModel? list)
|
||||||
|
{
|
||||||
|
_currentList = list;
|
||||||
|
Items.Clear();
|
||||||
|
if (list is null) return;
|
||||||
|
|
||||||
|
HeaderTitle = list.Name;
|
||||||
|
HeaderEyebrow = DateTime.Now.ToString("dddd · MMM dd").ToUpperInvariant();
|
||||||
|
|
||||||
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
var all = await db.Tasks
|
||||||
|
.Include(t => t.List)
|
||||||
|
.Include(t => t.Worktree)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
IEnumerable<TaskEntity> filtered = list.Kind switch
|
||||||
|
{
|
||||||
|
ListKind.Smart when list.Id == "smart:my-day" => all.Where(t => t.IsMyDay),
|
||||||
|
ListKind.Smart when list.Id == "smart:important" => all.Where(t => t.IsStarred),
|
||||||
|
ListKind.Smart when list.Id == "smart:planned" => all.Where(t => t.ScheduledFor != null),
|
||||||
|
ListKind.Virtual when list.Id == "virtual:running" => all.Where(t => t.Status == TaskStatus.Running),
|
||||||
|
ListKind.Virtual when list.Id == "virtual:review" => all.Where(t => t.Status == TaskStatus.Done && t.Worktree?.State == WorktreeState.Active),
|
||||||
|
ListKind.User => all.Where(t => $"user:{t.ListId}" == list.Id),
|
||||||
|
_ => Enumerable.Empty<TaskEntity>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var t in filtered)
|
||||||
|
Items.Add(TaskRowViewModel.FromEntity(t));
|
||||||
|
|
||||||
|
UpdateSubtitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSubtitle()
|
||||||
|
{
|
||||||
|
var open = Items.Count(i => !i.Done);
|
||||||
|
var running = Items.Count(i => i.Status == TaskStatus.Running);
|
||||||
|
var review = Items.Count(i => i.Status == TaskStatus.Done && i.Branch != null);
|
||||||
|
Subtitle = $"{open} open · {running} running · {review} in review";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task AddAsync()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(NewTaskTitle) || _currentList?.Kind != ListKind.User) return;
|
||||||
|
var listId = _currentList.Id["user:".Length..];
|
||||||
|
var entity = new TaskEntity
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid().ToString("N"),
|
||||||
|
ListId = listId,
|
||||||
|
Title = NewTaskTitle.Trim(),
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
};
|
||||||
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
db.Tasks.Add(entity);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
Items.Insert(0, TaskRowViewModel.FromEntity(entity));
|
||||||
|
NewTaskTitle = "";
|
||||||
|
UpdateSubtitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ToggleDoneAsync(TaskRowViewModel row)
|
||||||
|
{
|
||||||
|
row.Done = !row.Done;
|
||||||
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == row.Id);
|
||||||
|
if (entity != null)
|
||||||
|
{
|
||||||
|
entity.Status = row.Done ? TaskStatus.Done : TaskStatus.Manual;
|
||||||
|
row.Status = entity.Status;
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
UpdateSubtitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ToggleStarAsync(TaskRowViewModel row)
|
||||||
|
{
|
||||||
|
row.IsStarred = !row.IsStarred;
|
||||||
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == row.Id);
|
||||||
|
if (entity != null)
|
||||||
|
{
|
||||||
|
entity.IsStarred = row.IsStarred;
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void Select(TaskRowViewModel row) => SelectedTask = row;
|
||||||
|
|
||||||
|
partial void OnSelectedTaskChanged(TaskRowViewModel? value)
|
||||||
|
{
|
||||||
|
foreach (var i in Items) i.IsSelected = ReferenceEquals(i, value);
|
||||||
SelectionChanged?.Invoke(this, EventArgs.Empty);
|
SelectionChanged?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
34
src/ClaudeDo.Ui/Views/Islands/SessionTerminalView.axaml
Normal file
34
src/ClaudeDo.Ui/Views/Islands/SessionTerminalView.axaml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Islands"
|
||||||
|
x:Class="ClaudeDo.Ui.Views.Islands.SessionTerminalView"
|
||||||
|
x:DataType="vm:DetailsIslandViewModel">
|
||||||
|
<Border Classes="terminal" Padding="10">
|
||||||
|
<DockPanel LastChildFill="True">
|
||||||
|
<!-- Prompt input bar (docked to bottom) -->
|
||||||
|
<Grid DockPanel.Dock="Bottom" ColumnDefinitions="Auto,*" Margin="0,8,0,0">
|
||||||
|
<TextBlock Grid.Column="0" Text="[you]" FontFamily="{DynamicResource MonoFamily}"
|
||||||
|
FontSize="11" Foreground="{DynamicResource MossBrush}"
|
||||||
|
VerticalAlignment="Center" Margin="0,0,8,0"/>
|
||||||
|
<TextBox Grid.Column="1" Text="{Binding PromptInput, Mode=TwoWay}"
|
||||||
|
FontFamily="{DynamicResource MonoFamily}" FontSize="11">
|
||||||
|
<TextBox.KeyBindings>
|
||||||
|
<KeyBinding Gesture="Enter" Command="{Binding SendPromptCommand}"/>
|
||||||
|
</TextBox.KeyBindings>
|
||||||
|
</TextBox>
|
||||||
|
</Grid>
|
||||||
|
<!-- Log output -->
|
||||||
|
<ScrollViewer Name="LogScroll" VerticalScrollBarVisibility="Auto">
|
||||||
|
<ItemsControl ItemsSource="{Binding Log}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate x:CompileBindings="False" DataType="vm:LogLineViewModel">
|
||||||
|
<TextBlock Text="{Binding Text}" Classes="{Binding ClassName}"
|
||||||
|
FontFamily="{DynamicResource MonoFamily}" FontSize="11"
|
||||||
|
TextWrapping="Wrap"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
Reference in New Issue
Block a user