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:
@@ -1,12 +1,127 @@
|
||||
using System.Collections.ObjectModel;
|
||||
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;
|
||||
|
||||
public sealed partial class TasksIslandViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||
private ListNavItemViewModel? _currentList;
|
||||
|
||||
public event EventHandler? SelectionChanged;
|
||||
|
||||
public ObservableCollection<TaskRowViewModel> Items { get; } = new();
|
||||
|
||||
[ObservableProperty] private string _newTaskTitle = "";
|
||||
[ObservableProperty] private TaskRowViewModel? _selectedTask;
|
||||
public void LoadForList(ListNavItemViewModel? list) { /* Phase 5 */ }
|
||||
partial void OnSelectedTaskChanged(TaskRowViewModel? value) =>
|
||||
[ObservableProperty] private string _headerTitle = "";
|
||||
[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);
|
||||
}
|
||||
}
|
||||
|
||||
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