feat(ui): make TaskDetailViewModel editable with auto-save and tag CRUD
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,12 +16,18 @@ public partial class TaskDetailViewModel : ViewModelBase
|
|||||||
private readonly ListRepository _listRepo;
|
private readonly ListRepository _listRepo;
|
||||||
private readonly GitService _git;
|
private readonly GitService _git;
|
||||||
private readonly WorkerClient _worker;
|
private readonly WorkerClient _worker;
|
||||||
|
private readonly TagRepository _tagRepo;
|
||||||
|
|
||||||
[ObservableProperty] private string _title = "";
|
[ObservableProperty] private string _title = "";
|
||||||
[ObservableProperty] private string? _description;
|
[ObservableProperty] private string? _description;
|
||||||
[ObservableProperty] private string? _result;
|
[ObservableProperty] private string? _result;
|
||||||
[ObservableProperty] private string? _logPath;
|
[ObservableProperty] private string? _logPath;
|
||||||
[ObservableProperty] private string _statusText = "";
|
[ObservableProperty] private string _statusText = "";
|
||||||
|
[ObservableProperty] private string _statusChoice = "Manual";
|
||||||
|
[ObservableProperty] private string _commitType = "chore";
|
||||||
|
|
||||||
|
public static string[] StatusChoices { get; } = ["Manual", "Queued", "Running", "Done", "Failed"];
|
||||||
|
public static string[] CommitTypes { get; } = ["feat", "fix", "refactor", "docs", "test", "chore", "ci", "perf", "style", "build"];
|
||||||
|
|
||||||
// Worktree
|
// Worktree
|
||||||
[ObservableProperty] private bool _hasWorktree;
|
[ObservableProperty] private bool _hasWorktree;
|
||||||
@@ -32,19 +38,25 @@ public partial class TaskDetailViewModel : ViewModelBase
|
|||||||
|
|
||||||
// Live stream
|
// Live stream
|
||||||
public ObservableCollection<string> LiveLines { get; } = new();
|
public ObservableCollection<string> LiveLines { get; } = new();
|
||||||
|
public ObservableCollection<TagEntity> Tags { get; } = new();
|
||||||
|
[ObservableProperty] private string _newTagInput = "";
|
||||||
|
|
||||||
private string? _taskId;
|
private string? _taskId;
|
||||||
private string? _listId;
|
private string? _listId;
|
||||||
|
private bool _isLoading;
|
||||||
private const int MaxLiveLines = 500;
|
private const int MaxLiveLines = 500;
|
||||||
|
|
||||||
|
public event Action<string>? TaskChanged;
|
||||||
|
|
||||||
public TaskDetailViewModel(TaskRepository taskRepo, WorktreeRepository worktreeRepo,
|
public TaskDetailViewModel(TaskRepository taskRepo, WorktreeRepository worktreeRepo,
|
||||||
ListRepository listRepo, GitService git, WorkerClient worker)
|
ListRepository listRepo, GitService git, WorkerClient worker, TagRepository tagRepo)
|
||||||
{
|
{
|
||||||
_taskRepo = taskRepo;
|
_taskRepo = taskRepo;
|
||||||
_worktreeRepo = worktreeRepo;
|
_worktreeRepo = worktreeRepo;
|
||||||
_listRepo = listRepo;
|
_listRepo = listRepo;
|
||||||
_git = git;
|
_git = git;
|
||||||
_worker = worker;
|
_worker = worker;
|
||||||
|
_tagRepo = tagRepo;
|
||||||
|
|
||||||
worker.TaskMessageEvent += OnTaskMessage;
|
worker.TaskMessageEvent += OnTaskMessage;
|
||||||
worker.WorktreeUpdatedEvent += OnWorktreeUpdated;
|
worker.WorktreeUpdatedEvent += OnWorktreeUpdated;
|
||||||
@@ -59,16 +71,77 @@ public partial class TaskDetailViewModel : ViewModelBase
|
|||||||
var task = await _taskRepo.GetByIdAsync(taskId);
|
var task = await _taskRepo.GetByIdAsync(taskId);
|
||||||
if (task is null) return;
|
if (task is null) return;
|
||||||
|
|
||||||
_listId = task.ListId;
|
_isLoading = true;
|
||||||
Title = task.Title;
|
try
|
||||||
Description = task.Description;
|
{
|
||||||
Result = task.Result;
|
_listId = task.ListId;
|
||||||
LogPath = task.LogPath;
|
Title = task.Title;
|
||||||
StatusText = task.Status.ToString().ToLowerInvariant();
|
Description = task.Description;
|
||||||
|
Result = task.Result;
|
||||||
|
LogPath = task.LogPath;
|
||||||
|
StatusText = task.Status.ToString().ToLowerInvariant();
|
||||||
|
StatusChoice = task.Status.ToString();
|
||||||
|
CommitType = task.CommitType;
|
||||||
|
|
||||||
|
Tags.Clear();
|
||||||
|
var tags = await _taskRepo.GetTagsAsync(taskId);
|
||||||
|
foreach (var tag in tags)
|
||||||
|
Tags.Add(tag);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
await LoadWorktreeAsync(taskId);
|
await LoadWorktreeAsync(taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SaveAsync()
|
||||||
|
{
|
||||||
|
if (_isLoading || _taskId is null) return;
|
||||||
|
|
||||||
|
var entity = await _taskRepo.GetByIdAsync(_taskId);
|
||||||
|
if (entity is null) return;
|
||||||
|
|
||||||
|
entity.Title = Title;
|
||||||
|
entity.Description = Description;
|
||||||
|
entity.CommitType = CommitType;
|
||||||
|
|
||||||
|
if (Enum.TryParse<Data.Models.TaskStatus>(StatusChoice, true, out var status))
|
||||||
|
entity.Status = status;
|
||||||
|
|
||||||
|
await _taskRepo.UpdateAsync(entity);
|
||||||
|
StatusText = entity.Status.ToString().ToLowerInvariant();
|
||||||
|
TaskChanged?.Invoke(_taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task AddTag()
|
||||||
|
{
|
||||||
|
var name = NewTagInput.Trim();
|
||||||
|
if (string.IsNullOrEmpty(name) || _taskId is null) return;
|
||||||
|
|
||||||
|
var tagId = await _tagRepo.GetOrCreateAsync(name);
|
||||||
|
await _taskRepo.AddTagAsync(_taskId, tagId);
|
||||||
|
|
||||||
|
Tags.Clear();
|
||||||
|
var tags = await _taskRepo.GetTagsAsync(_taskId);
|
||||||
|
foreach (var tag in tags)
|
||||||
|
Tags.Add(tag);
|
||||||
|
|
||||||
|
NewTagInput = "";
|
||||||
|
TaskChanged?.Invoke(_taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task RemoveTag(TagEntity tag)
|
||||||
|
{
|
||||||
|
if (_taskId is null) return;
|
||||||
|
await _taskRepo.RemoveTagAsync(_taskId, tag.Id);
|
||||||
|
Tags.Remove(tag);
|
||||||
|
TaskChanged?.Invoke(_taskId);
|
||||||
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
_taskId = null;
|
_taskId = null;
|
||||||
@@ -80,6 +153,10 @@ public partial class TaskDetailViewModel : ViewModelBase
|
|||||||
StatusText = "";
|
StatusText = "";
|
||||||
HasWorktree = false;
|
HasWorktree = false;
|
||||||
LiveLines.Clear();
|
LiveLines.Clear();
|
||||||
|
Tags.Clear();
|
||||||
|
NewTagInput = "";
|
||||||
|
StatusChoice = "Manual";
|
||||||
|
CommitType = "chore";
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadWorktreeAsync(string taskId)
|
private async Task LoadWorktreeAsync(string taskId)
|
||||||
|
|||||||
Reference in New Issue
Block a user