Die oberste Sidebar-Ebene ist jetzt eine gemischte TopLevel-Collection, so dass eine Liste auch oberhalb eines Ordners stehen kann. DropAsync deckt alle vier Quelle/Ziel-Kombinationen ab, ReorderSidebarAsync numeriert die gesamte sichtbare Reihenfolge ueber lists und list_folders hinweg.
290 lines
15 KiB
XML
290 lines
15 KiB
XML
<UserControl xmlns="https://github.com/avaloniaui"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Islands"
|
|
xmlns:converters="using:ClaudeDo.Ui.Converters"
|
|
xmlns:loc="using:ClaudeDo.Ui.Localization"
|
|
x:Class="ClaudeDo.Ui.Views.Islands.ListsIslandView"
|
|
x:DataType="vm:ListsIslandViewModel">
|
|
<!-- Implicit, type-matched templates: the sidebar's top level is one mixed ItemsControl over
|
|
TopLevel (folders and folder-less lists interleaved), and each folder's children resolve the
|
|
list template the same way. -->
|
|
<UserControl.DataTemplates>
|
|
<!-- One user-list row. Shared by the top level and by each folder's children, so the
|
|
row (context menu, drag handlers, drop indicators) exists once. -->
|
|
<DataTemplate DataType="vm:ListNavItemViewModel">
|
|
<Grid RowDefinitions="Auto,Auto,Auto">
|
|
|
|
<!-- Above-row drop indicator -->
|
|
<Border Grid.Row="0" Height="2" VerticalAlignment="Center" Margin="4,0"
|
|
Background="{DynamicResource MossBrush}" CornerRadius="1"
|
|
IsVisible="{Binding DropHintAbove}"/>
|
|
|
|
<Border Grid.Row="1" Classes="list-item" Classes.active="{Binding IsActive}"
|
|
Classes.drop-target="{Binding IsTaskDropTarget}"
|
|
Tapped="OnItemTapped"
|
|
DragDrop.AllowDrop="True"
|
|
DragDrop.DragOver="OnListDragOver"
|
|
DragDrop.Drop="OnListDrop">
|
|
<Border.ContextMenu>
|
|
<ContextMenu>
|
|
<MenuItem Header="{loc:Tr lists.contextWorktrees}"
|
|
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenWorktreesOverviewCommand}"
|
|
CommandParameter="{Binding}"/>
|
|
<Separator IsVisible="{Binding WorkingDir, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
|
<MenuItem Header="{loc:Tr lists.contextOpenExplorer}"
|
|
IsVisible="{Binding WorkingDir, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
|
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenInExplorerCommand}"
|
|
CommandParameter="{Binding}"/>
|
|
<MenuItem Header="{loc:Tr lists.contextOpenFindings}"
|
|
IsVisible="{Binding WorkingDir, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
|
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenFindingsCommand}"
|
|
CommandParameter="{Binding}"/>
|
|
<MenuItem Header="{loc:Tr lists.contextOpenTerminal}"
|
|
IsVisible="{Binding WorkingDir, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
|
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenInTerminalCommand}"
|
|
CommandParameter="{Binding}"/>
|
|
<MenuItem Header="{loc:Tr lists.contextImportTickets}"
|
|
IsVisible="{Binding HasTicketProject}"
|
|
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).ImportTicketsCommand}"
|
|
CommandParameter="{Binding}"/>
|
|
<Separator/>
|
|
<MenuItem Header="{loc:Tr lists.contextRemoveFromFolder}"
|
|
IsVisible="{Binding FolderId, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
|
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).RemoveFromFolderCommand}"
|
|
CommandParameter="{Binding}"/>
|
|
<MenuItem Header="{loc:Tr lists.contextSettings}"
|
|
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenListSettingsCommand}"
|
|
CommandParameter="{Binding}"/>
|
|
</ContextMenu>
|
|
</Border.ContextMenu>
|
|
<Grid ColumnDefinitions="20,*,Auto">
|
|
<!-- Left accent bar for active state -->
|
|
<Border Grid.Column="0" Grid.ColumnSpan="3"
|
|
Background="Transparent"
|
|
CornerRadius="8" IsHitTestVisible="False"
|
|
IsVisible="{Binding IsActive}">
|
|
<Border Width="2" Height="16"
|
|
Background="{DynamicResource AccentBrush}"
|
|
CornerRadius="1"
|
|
HorizontalAlignment="Left" VerticalAlignment="Center"
|
|
Margin="-8,0,0,0"/>
|
|
</Border>
|
|
<!-- Icon (list-settings → Icon tab; "Folder" when unset) -->
|
|
<PathIcon Grid.Column="0" Classes="list-icon"
|
|
Width="14" Height="14"
|
|
Data="{Binding IconKey, Converter={StaticResource IconKey}}"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
|
<!-- Name -->
|
|
<TextBlock Grid.Column="1" Classes="list-label"
|
|
Text="{Binding Name}"
|
|
VerticalAlignment="Center" Margin="8,0"
|
|
Foreground="{DynamicResource TextDimBrush}" FontSize="{StaticResource FontSizeBody}"/>
|
|
<!-- Count -->
|
|
<TextBlock Grid.Column="2" Classes="list-count"
|
|
Text="{Binding Count}"/>
|
|
</Grid>
|
|
</Border>
|
|
|
|
<!-- Below-row drop indicator (last item only) -->
|
|
<Border Grid.Row="2" Height="2" VerticalAlignment="Center" Margin="4,0"
|
|
Background="{DynamicResource MossBrush}" CornerRadius="1"
|
|
IsVisible="{Binding DropHintBelow}"/>
|
|
</Grid>
|
|
</DataTemplate>
|
|
|
|
<!-- One folder: header (never selectable — tapping folds it) plus its child lists. The two
|
|
drop indicators sit around the whole block, so "below this folder" doesn't draw a line
|
|
between the header and its children. -->
|
|
<DataTemplate DataType="vm:ListFolderNavItemViewModel">
|
|
<Grid RowDefinitions="Auto,Auto,Auto">
|
|
<Border Grid.Row="0" Height="2" VerticalAlignment="Center" Margin="4,0"
|
|
Background="{DynamicResource MossBrush}" CornerRadius="1"
|
|
IsVisible="{Binding DropHintAbove}"/>
|
|
|
|
<StackPanel Grid.Row="1">
|
|
<Border Classes="list-item"
|
|
Classes.drop-target="{Binding IsDropTarget}"
|
|
Tapped="OnFolderTapped"
|
|
DragDrop.AllowDrop="True"
|
|
DragDrop.DragOver="OnFolderDragOver"
|
|
DragDrop.Drop="OnFolderDrop">
|
|
<Border.ContextMenu>
|
|
<ContextMenu>
|
|
<MenuItem Header="{loc:Tr lists.contextFolderSettings}"
|
|
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenFolderSettingsCommand}"
|
|
CommandParameter="{Binding}"/>
|
|
</ContextMenu>
|
|
</Border.ContextMenu>
|
|
<Grid ColumnDefinitions="12,20,*,Auto">
|
|
<PathIcon Grid.Column="0" Classes="list-icon"
|
|
Width="9" Height="9"
|
|
Data="{StaticResource Icon.ChevronRight}"
|
|
IsVisible="{Binding IsCollapsed}"
|
|
HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
|
<PathIcon Grid.Column="0" Classes="list-icon"
|
|
Width="9" Height="9"
|
|
Data="{StaticResource Icon.ChevronDown}"
|
|
IsVisible="{Binding !IsCollapsed}"
|
|
HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
|
<PathIcon Grid.Column="1" Classes="list-icon"
|
|
Width="14" Height="14"
|
|
Data="{Binding IconKey, Converter={StaticResource IconKey}}"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
|
<TextBlock Grid.Column="2" Classes="list-label"
|
|
Text="{Binding Name}"
|
|
VerticalAlignment="Center" Margin="8,0"
|
|
Foreground="{DynamicResource TextDimBrush}" FontSize="{StaticResource FontSizeBody}"/>
|
|
<TextBlock Grid.Column="3" Classes="list-count"
|
|
Text="{Binding Count}"/>
|
|
</Grid>
|
|
</Border>
|
|
|
|
<ItemsControl ItemsSource="{Binding Lists}"
|
|
IsVisible="{Binding !IsCollapsed}"
|
|
Margin="14,0,0,0"/>
|
|
</StackPanel>
|
|
|
|
<Border Grid.Row="2" Height="2" VerticalAlignment="Center" Margin="4,0"
|
|
Background="{DynamicResource MossBrush}" CornerRadius="1"
|
|
IsVisible="{Binding DropHintBelow}"/>
|
|
</Grid>
|
|
</DataTemplate>
|
|
</UserControl.DataTemplates>
|
|
|
|
<DockPanel LastChildFill="True">
|
|
|
|
<!-- ── Header ── -->
|
|
<Border DockPanel.Dock="Top" Classes="island-header">
|
|
<StackPanel Spacing="4">
|
|
<TextBlock Classes="heading" Text="{loc:Tr lists.heading}"/>
|
|
|
|
<!-- Search row -->
|
|
<Border Classes="search-wrap" Margin="0,8,0,12">
|
|
<Grid ColumnDefinitions="Auto,*,Auto" VerticalAlignment="Center">
|
|
<PathIcon Grid.Column="0" Width="14" Height="14"
|
|
Data="{StaticResource Icon.Search}"
|
|
Foreground="{DynamicResource TextFaintBrush}"
|
|
Margin="2,0,0,0"/>
|
|
<TextBox Grid.Column="1" x:Name="SearchBox" Classes="search-inner"
|
|
PlaceholderText="{loc:Tr lists.searchPlaceholder}"
|
|
Text="{Binding SearchText, Mode=TwoWay}"/>
|
|
<Border Grid.Column="2" Classes="kbd" Margin="0,0,2,0">
|
|
<TextBlock Text="{loc:Tr lists.searchKbd}"/>
|
|
</Border>
|
|
</Grid>
|
|
</Border>
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
<!-- ── No-repo banner — visible until any User list has a linked working dir; no
|
|
dismiss, no persisted state, comes back if the last repo link is removed. ── -->
|
|
<Border DockPanel.Dock="Top"
|
|
IsVisible="{Binding HasNoLinkedRepo}"
|
|
Margin="12,0,12,12"
|
|
Background="{DynamicResource DeepBrush}"
|
|
BorderBrush="{DynamicResource LineBrush}"
|
|
BorderThickness="1"
|
|
CornerRadius="6"
|
|
Padding="14,10">
|
|
<StackPanel Spacing="8">
|
|
<TextBlock Classes="body" Text="{loc:Tr lists.noRepoBannerText}" TextWrapping="Wrap"/>
|
|
<Button Classes="btn" HorizontalAlignment="Right"
|
|
Content="{loc:Tr lists.noRepoBannerButton}"
|
|
Command="{Binding OpenRepoImportCommand}"/>
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
<!-- ── Footer ── -->
|
|
<Border DockPanel.Dock="Bottom"
|
|
BorderBrush="{DynamicResource LineBrush}" BorderThickness="0,1,0,0"
|
|
Padding="12,10">
|
|
<Grid ColumnDefinitions="Auto,*,Auto" VerticalAlignment="Center">
|
|
<!-- Avatar circle -->
|
|
<Border Grid.Column="0" Classes="avatar-circle"
|
|
VerticalAlignment="Center">
|
|
<TextBlock Classes="eyebrow"
|
|
Text="{Binding UserInitials}"
|
|
FontWeight="SemiBold"
|
|
Foreground="{DynamicResource DeepBrush}"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
|
</Border>
|
|
<!-- Name + machine -->
|
|
<StackPanel Grid.Column="1" Margin="8,0" Spacing="1" VerticalAlignment="Center">
|
|
<TextBlock Classes="title" Text="{Binding UserName}"/>
|
|
<TextBlock Classes="meta" Text="{Binding MachineNameLocal}"/>
|
|
</StackPanel>
|
|
<!-- Settings button -->
|
|
<Button Grid.Column="2" Classes="icon-btn" VerticalAlignment="Center"
|
|
Command="{Binding OpenSettingsCommand}"
|
|
ToolTip.Tip="{loc:Tr lists.settingsTip}">
|
|
<PathIcon Data="{StaticResource Icon.Settings}" Width="15" Height="15"/>
|
|
</Button>
|
|
</Grid>
|
|
</Border>
|
|
|
|
<!-- ── Scrollable body ── -->
|
|
<ScrollViewer>
|
|
<StackPanel Margin="6,0,6,4">
|
|
|
|
<!-- SMART LISTS section -->
|
|
<TextBlock Classes="section-label" Text="{loc:Tr lists.smartListsLabel}" Margin="10,10,10,4"/>
|
|
<ItemsControl ItemsSource="{Binding SmartLists}">
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate DataType="vm:ListNavItemViewModel">
|
|
<Border Classes="list-item" Classes.active="{Binding IsActive}"
|
|
Tapped="OnItemTapped">
|
|
<Grid ColumnDefinitions="20,*,Auto">
|
|
<!-- Left accent bar for active state -->
|
|
<Border Grid.Column="0" Grid.ColumnSpan="3"
|
|
Background="Transparent"
|
|
CornerRadius="8" IsHitTestVisible="False"
|
|
IsVisible="{Binding IsActive}">
|
|
<Border Width="2" Height="16"
|
|
Background="{DynamicResource AccentBrush}"
|
|
CornerRadius="1"
|
|
HorizontalAlignment="Left" VerticalAlignment="Center"
|
|
Margin="-8,0,0,0"/>
|
|
</Border>
|
|
<!-- Icon -->
|
|
<PathIcon Grid.Column="0" Classes="list-icon"
|
|
Width="14" Height="14"
|
|
Data="{Binding IconKey, Converter={StaticResource IconKey}}"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
|
<!-- Name -->
|
|
<TextBlock Grid.Column="1" Classes="list-label"
|
|
Text="{Binding Name}"
|
|
VerticalAlignment="Center" Margin="8,0"
|
|
Foreground="{DynamicResource TextDimBrush}" FontSize="{StaticResource FontSizeBody}"/>
|
|
<!-- Count -->
|
|
<TextBlock Grid.Column="2" Classes="list-count"
|
|
Text="{Binding Count}"/>
|
|
</Grid>
|
|
</Border>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
|
|
<!-- MY LISTS section — add-list + repo-import sit next to the label -->
|
|
<Grid ColumnDefinitions="*,Auto,Auto" Margin="10,10,6,4" VerticalAlignment="Center">
|
|
<TextBlock Grid.Column="0" Classes="section-label"
|
|
Text="{loc:Tr lists.myListsLabel}" VerticalAlignment="Center"/>
|
|
<Button Grid.Column="1" Classes="icon-btn"
|
|
Command="{Binding CreateListCommand}"
|
|
ToolTip.Tip="{loc:Tr lists.newList}">
|
|
<PathIcon Data="{StaticResource Icon.Plus}" Width="13" Height="13"/>
|
|
</Button>
|
|
<Button Grid.Column="2" Classes="icon-btn"
|
|
Command="{Binding OpenRepoImportCommand}"
|
|
ToolTip.Tip="{loc:Tr lists.addReposTip}">
|
|
<PathIcon Data="{StaticResource Icon.Folder}" Width="14" Height="14"/>
|
|
</Button>
|
|
</Grid>
|
|
<!-- Folders and folder-less lists in one sequence, so either can be dragged above the
|
|
other; each item picks its template from UserControl.DataTemplates by type. -->
|
|
<ItemsControl ItemsSource="{Binding TopLevel}"/>
|
|
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
</DockPanel>
|
|
</UserControl>
|