feat(ui): list reordering, quick actions, and resizable modals
- Drag-to-reorder user lists in the sidebar, persisted via a new list sort_order column (AddListSortOrder migration, backfilled by creation time) and ListRepository.ReorderAsync - "Open in Explorer" / "Open in Terminal" context-menu actions on lists - "Clear all completed" button on the Tasks island - Inline-edit subtask titles (empty text deletes the step) and click-to-copy task ID in the Details island - Make modal and planning windows resizable (BorderOnly decorations with min sizes) instead of fixed-size borderless Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,52 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
|
||||
finally { _worktreesOverviewOpen = false; }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenInExplorer(ListNavItemViewModel? row)
|
||||
{
|
||||
var dir = row?.WorkingDir;
|
||||
if (string.IsNullOrWhiteSpace(dir) || !System.IO.Directory.Exists(dir)) return;
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = dir,
|
||||
UseShellExecute = true,
|
||||
});
|
||||
}
|
||||
catch { /* best-effort */ }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenInTerminal(ListNavItemViewModel? row)
|
||||
{
|
||||
var dir = row?.WorkingDir;
|
||||
if (string.IsNullOrWhiteSpace(dir) || !System.IO.Directory.Exists(dir)) return;
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "wt.exe",
|
||||
Arguments = $"-d \"{dir}\"",
|
||||
UseShellExecute = true,
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Windows Terminal not installed — fall back to a plain console at the directory.
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
WorkingDirectory = dir,
|
||||
UseShellExecute = true,
|
||||
});
|
||||
}
|
||||
catch { /* best-effort */ }
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<ListNavItemViewModel> Items { get; } = new();
|
||||
public ObservableCollection<ListNavItemViewModel> SmartLists { get; } = new();
|
||||
public ObservableCollection<ListNavItemViewModel> UserLists { get; } = new();
|
||||
@@ -231,6 +277,57 @@ public sealed partial class ListsIslandViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearDropHints()
|
||||
{
|
||||
foreach (var r in UserLists)
|
||||
{
|
||||
r.DropHintAbove = false;
|
||||
r.DropHintBelow = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDropHint(ListNavItemViewModel target, bool placeBelow)
|
||||
{
|
||||
foreach (var r in UserLists)
|
||||
{
|
||||
var isTarget = ReferenceEquals(r, target);
|
||||
r.DropHintAbove = isTarget && !placeBelow;
|
||||
r.DropHintBelow = isTarget && placeBelow;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ReorderAsync(ListNavItemViewModel source, ListNavItemViewModel target, bool placeBelow)
|
||||
{
|
||||
if (source.Kind != ListKind.User || target.Kind != ListKind.User) return;
|
||||
if (ReferenceEquals(source, target)) return;
|
||||
|
||||
MoveWithinCollection(UserLists, source, target, placeBelow);
|
||||
|
||||
var orderedIds = UserLists.Select(i => i.Id["user:".Length..]).ToList();
|
||||
await using var ctx = await _dbFactory.CreateDbContextAsync();
|
||||
var lists = new ListRepository(ctx);
|
||||
await lists.ReorderAsync(orderedIds);
|
||||
}
|
||||
|
||||
private static void MoveWithinCollection(
|
||||
ObservableCollection<ListNavItemViewModel> coll,
|
||||
ListNavItemViewModel source,
|
||||
ListNavItemViewModel target,
|
||||
bool placeBelow)
|
||||
{
|
||||
var srcIdx = coll.IndexOf(source);
|
||||
var tgtIdx = coll.IndexOf(target);
|
||||
if (srcIdx < 0 || tgtIdx < 0 || srcIdx == tgtIdx) return;
|
||||
|
||||
var finalIdx = placeBelow ? tgtIdx + 1 : tgtIdx;
|
||||
if (srcIdx < finalIdx) finalIdx--;
|
||||
if (finalIdx < 0) finalIdx = 0;
|
||||
if (finalIdx >= coll.Count) finalIdx = coll.Count - 1;
|
||||
if (finalIdx == srcIdx) return;
|
||||
|
||||
coll.Move(srcIdx, finalIdx);
|
||||
}
|
||||
|
||||
partial void OnSelectedListChanged(ListNavItemViewModel? value)
|
||||
{
|
||||
foreach (var i in Items) i.IsActive = ReferenceEquals(i, value);
|
||||
|
||||
Reference in New Issue
Block a user