feat(ui): collapsible folders for the list sidebar
Folders render above the top-level lists, each with its own collapsible children; tapping a folder header folds it instead of selecting it. Lists move in by dragging onto a folder header (or onto a row inside it) and out by dropping between top-level rows or via the context menu. Creating a folder is a toggle in the New-list modal, not a third sidebar button: it reduces the modal to name + icon and writes a folder row instead of the list row the create flow pre-created.
This commit is contained in:
@@ -69,6 +69,60 @@ public partial class ListsIslandView : UserControl
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFolderTapped(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Border { DataContext: ListFolderNavItemViewModel folder }
|
||||
&& DataContext is ListsIslandViewModel vm)
|
||||
vm.ToggleFolderCommand.Execute(folder);
|
||||
}
|
||||
|
||||
// Dropping onto the folder *header* appends the list to that folder — the only route into a
|
||||
// collapsed or empty folder, where there's no child row to drop next to.
|
||||
private void OnFolderDragOver(object? sender, DragEventArgs e)
|
||||
{
|
||||
if (DataContext is not ListsIslandViewModel vm) { e.DragEffects = DragDropEffects.None; return; }
|
||||
if ((!e.DataTransfer?.Contains(ListRowFormat) ?? true)
|
||||
|| sender is not Border { DataContext: ListFolderNavItemViewModel folder })
|
||||
{
|
||||
e.DragEffects = DragDropEffects.None;
|
||||
vm.ClearDropHints();
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceId = e.DataTransfer?.TryGetValue(ListRowFormat);
|
||||
var source = sourceId is null ? null : vm.UserLists.FirstOrDefault(r => r.Id == sourceId);
|
||||
if (source is null || source.FolderId == folder.Id)
|
||||
{
|
||||
e.DragEffects = DragDropEffects.None;
|
||||
vm.ClearDropHints();
|
||||
return;
|
||||
}
|
||||
|
||||
vm.SetFolderDropTarget(folder);
|
||||
e.DragEffects = DragDropEffects.Move;
|
||||
}
|
||||
|
||||
private async void OnFolderDrop(object? sender, DragEventArgs e)
|
||||
{
|
||||
if (DataContext is not ListsIslandViewModel vm) return;
|
||||
try
|
||||
{
|
||||
if (sender is not Border { DataContext: ListFolderNavItemViewModel folder }) return;
|
||||
|
||||
var sourceId = e.DataTransfer?.TryGetValue(ListRowFormat);
|
||||
var source = sourceId is null ? null : vm.UserLists.FirstOrDefault(r => r.Id == sourceId);
|
||||
if (source is null) return;
|
||||
|
||||
vm.ClearDropHints();
|
||||
await vm.MoveToFolderAsync(source, folder);
|
||||
}
|
||||
catch
|
||||
{
|
||||
vm.ClearDropHints();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnListDragOver(object? sender, DragEventArgs e)
|
||||
{
|
||||
if (DataContext is not ListsIslandViewModel vm) { e.DragEffects = DragDropEffects.None; return; }
|
||||
@@ -150,11 +204,14 @@ public partial class ListsIslandView : UserControl
|
||||
return null;
|
||||
}
|
||||
|
||||
// Next row *within the same container* — the row below the last list of a folder belongs to
|
||||
// another folder (or to the top level), so it is not a "drop above" candidate here.
|
||||
private static ListNavItemViewModel? FindNextUserList(ListsIslandViewModel vm, ListNavItemViewModel row)
|
||||
{
|
||||
var idx = vm.UserLists.IndexOf(row);
|
||||
var container = vm.ContainerOf(row);
|
||||
var idx = container.IndexOf(row);
|
||||
if (idx < 0) return null;
|
||||
return idx + 1 < vm.UserLists.Count ? vm.UserLists[idx + 1] : null;
|
||||
return idx + 1 < container.Count ? container[idx + 1] : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user