refactor(diff): single DiffViewer replaces DiffModal + WorktreeModal + PlanningDiff
This commit is contained in:
@@ -8,7 +8,6 @@ using Avalonia.Platform.Storage;
|
||||
using Avalonia.Reactive;
|
||||
using ClaudeDo.Ui.ViewModels.Islands;
|
||||
using ClaudeDo.Ui.Views.Modals;
|
||||
using ClaudeDo.Ui.Views.Planning;
|
||||
|
||||
namespace ClaudeDo.Ui.Views.Islands;
|
||||
|
||||
@@ -129,11 +128,11 @@ public partial class DetailsIslandView : UserControl
|
||||
vm.PropertyChanged += OnViewModelPropertyChanged;
|
||||
ApplyResizeStateForCurrentTask();
|
||||
|
||||
vm.Merge.ShowDiffModal = async (diffVm) =>
|
||||
vm.Merge.ShowDiffViewer = async (diffVm) =>
|
||||
{
|
||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||
if (owner == null) return;
|
||||
var modal = new DiffModalView { DataContext = diffVm };
|
||||
var modal = new DiffViewerView { DataContext = diffVm };
|
||||
await modal.ShowDialog(owner);
|
||||
};
|
||||
|
||||
@@ -145,14 +144,6 @@ public partial class DetailsIslandView : UserControl
|
||||
await modal.ShowDialog(owner);
|
||||
};
|
||||
|
||||
vm.Merge.ShowPlanningDiffModal = async (planningDiffVm) =>
|
||||
{
|
||||
var owner = TopLevel.GetTopLevel(this) as Window;
|
||||
if (owner == null) return;
|
||||
var modal = new PlanningDiffView { DataContext = planningDiffVm };
|
||||
await modal.ShowDialog(owner);
|
||||
};
|
||||
|
||||
vm.ConfirmAsync = ShowConfirmAsync;
|
||||
vm.ShowErrorAsync = ShowErrorDialogAsync;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ using ClaudeDo.Ui.ViewModels;
|
||||
using ClaudeDo.Ui.ViewModels.Islands;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
using ClaudeDo.Ui.Views.Modals;
|
||||
using ClaudeDo.Ui.Views.Planning;
|
||||
|
||||
namespace ClaudeDo.Ui.Views;
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Modals"
|
||||
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"
|
||||
xmlns:loc="using:ClaudeDo.Ui.Localization"
|
||||
x:Class="ClaudeDo.Ui.Views.Modals.DiffModalView"
|
||||
x:DataType="vm:DiffModalViewModel"
|
||||
Title="{loc:Tr modals.diff.windowTitle}"
|
||||
Width="1200" Height="800" MinWidth="700" MinHeight="450"
|
||||
CanResize="True"
|
||||
WindowDecorations="BorderOnly"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
ExtendClientAreaTitleBarHeightHint="-1"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Background="{DynamicResource SurfaceBrush}">
|
||||
|
||||
<Window.KeyBindings>
|
||||
<KeyBinding Gesture="Escape" Command="{Binding CloseCommand}"/>
|
||||
</Window.KeyBindings>
|
||||
|
||||
<ctl:ModalShell Title="{loc:Tr modals.diff.title}" CloseCommand="{Binding CloseCommand}">
|
||||
<ctl:ModalShell.Footer>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button Classes="btn" Content="{loc:Tr modals.diff.merge}" Command="{Binding MergeCommand}"/>
|
||||
</StackPanel>
|
||||
</ctl:ModalShell.Footer>
|
||||
|
||||
<!-- Body: two islands — file list | diff content -->
|
||||
<Grid ColumnDefinitions="280,12,*" Margin="16">
|
||||
|
||||
<!-- Files island -->
|
||||
<Border Grid.Column="0" Classes="island">
|
||||
<DockPanel>
|
||||
<Border DockPanel.Dock="Top" Classes="island-header">
|
||||
<TextBlock Classes="eyebrow" Text="{loc:Tr modals.diff.filesHeader}"/>
|
||||
</Border>
|
||||
<ListBox ItemsSource="{Binding Files}"
|
||||
SelectedItem="{Binding SelectedFile, Mode=TwoWay}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:DiffFileViewModel">
|
||||
<Border Padding="10,8" Background="Transparent">
|
||||
<StackPanel Spacing="4">
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<Border Grid.Column="0" Tag="{Binding StatusCode}"
|
||||
CornerRadius="3" Padding="4,0" Margin="0,0,8,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding StatusCode}"
|
||||
FontFamily="{DynamicResource MonoFont}"
|
||||
FontSize="{StaticResource FontSizeEyebrow}"
|
||||
Foreground="{DynamicResource TextBrush}"/>
|
||||
</Border>
|
||||
<TextBlock Grid.Column="1" Classes="path-mono" Text="{Binding Path}"
|
||||
VerticalAlignment="Center"
|
||||
TextTrimming="PrefixCharacterEllipsis"/>
|
||||
</Grid>
|
||||
<StackPanel Orientation="Horizontal" Spacing="6"
|
||||
IsVisible="{Binding !IsBinary}">
|
||||
<Border Classes="chip" Padding="5,2">
|
||||
<TextBlock Foreground="{DynamicResource MossBrightBrush}"
|
||||
Text="{Binding Additions, StringFormat='+{0}'}"/>
|
||||
</Border>
|
||||
<Border Classes="chip" Padding="5,2">
|
||||
<TextBlock Foreground="{DynamicResource BloodBrush}"
|
||||
Text="{Binding Deletions, StringFormat='−{0}'}"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Diff content island -->
|
||||
<Border Grid.Column="2" Classes="island">
|
||||
<DockPanel>
|
||||
<Border DockPanel.Dock="Top" Classes="island-header"
|
||||
IsVisible="{Binding SelectedFile, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<Border Grid.Column="0" Tag="{Binding SelectedFile.StatusCode}"
|
||||
CornerRadius="3" Padding="4,0" Margin="0,0,8,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding SelectedFile.StatusCode}"
|
||||
FontFamily="{DynamicResource MonoFont}"
|
||||
FontSize="{StaticResource FontSizeEyebrow}"
|
||||
Foreground="{DynamicResource TextBrush}"/>
|
||||
</Border>
|
||||
<TextBlock Grid.Column="1" Classes="path-mono" Text="{Binding SelectedFile.Path}"
|
||||
VerticalAlignment="Center"
|
||||
TextTrimming="PrefixCharacterEllipsis"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Grid Background="{DynamicResource VoidBrush}">
|
||||
<!-- Load / no-changes message -->
|
||||
<TextBlock Classes="body" Text="{Binding StatusMessage}"
|
||||
IsVisible="{Binding StatusMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<!-- Binary file -->
|
||||
<TextBlock Classes="body" Text="{loc:Tr modals.diff.binary}"
|
||||
Foreground="{DynamicResource TextMuteBrush}"
|
||||
IsVisible="{Binding SelectedFile.IsBinary}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<!-- Empty / no-content file -->
|
||||
<TextBlock Classes="body" Text="{loc:Tr modals.diff.empty}"
|
||||
Foreground="{DynamicResource TextMuteBrush}"
|
||||
IsVisible="{Binding SelectedFile.IsEmptyContent}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<!-- Diff content -->
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
IsVisible="{Binding SelectedFile.HasLines}">
|
||||
<ctl:DiffLinesView Lines="{Binding SelectedFile.Lines}"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ctl:ModalShell>
|
||||
</Window>
|
||||
@@ -1,45 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Animation.Easings;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Styling;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
|
||||
namespace ClaudeDo.Ui.Views.Modals;
|
||||
|
||||
public partial class DiffModalView : Window
|
||||
{
|
||||
public DiffModalView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
base.OnDataContextChanged(e);
|
||||
if (DataContext is DiffModalViewModel vm)
|
||||
vm.CloseAction = Close;
|
||||
}
|
||||
|
||||
protected override async void OnOpened(EventArgs e)
|
||||
{
|
||||
base.OnOpened(e);
|
||||
Opacity = 0;
|
||||
RenderTransform = new ScaleTransform(0.98, 0.98);
|
||||
var anim = new Avalonia.Animation.Animation
|
||||
{
|
||||
Duration = TimeSpan.FromMilliseconds(180),
|
||||
Easing = new CubicEaseOut(),
|
||||
FillMode = FillMode.Forward,
|
||||
Children =
|
||||
{
|
||||
new KeyFrame { Cue = new Cue(0), Setters = { new Setter(OpacityProperty, 0d) } },
|
||||
new KeyFrame { Cue = new Cue(1), Setters = { new Setter(OpacityProperty, 1d) } },
|
||||
}
|
||||
};
|
||||
await anim.RunAsync(this);
|
||||
Opacity = 1;
|
||||
RenderTransform = new ScaleTransform(1.0, 1.0);
|
||||
}
|
||||
}
|
||||
171
src/ClaudeDo.Ui/Views/Modals/DiffViewerView.axaml
Normal file
171
src/ClaudeDo.Ui/Views/Modals/DiffViewerView.axaml
Normal file
@@ -0,0 +1,171 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Modals"
|
||||
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"
|
||||
xmlns:loc="using:ClaudeDo.Ui.Localization"
|
||||
x:Class="ClaudeDo.Ui.Views.Modals.DiffViewerView"
|
||||
x:DataType="vm:DiffViewerViewModel"
|
||||
Title="{loc:Tr modals.diff.windowTitle}"
|
||||
Width="1200" Height="800" MinWidth="700" MinHeight="450"
|
||||
CanResize="True"
|
||||
WindowDecorations="BorderOnly"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
ExtendClientAreaTitleBarHeightHint="-1"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Background="{DynamicResource SurfaceBrush}">
|
||||
|
||||
<Window.KeyBindings>
|
||||
<KeyBinding Gesture="Escape" Command="{Binding CloseCommand}"/>
|
||||
</Window.KeyBindings>
|
||||
|
||||
<ctl:ModalShell Title="{loc:Tr modals.diff.title}" CloseCommand="{Binding CloseCommand}">
|
||||
<ctl:ModalShell.Footer>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button Classes="btn" Content="{loc:Tr modals.diff.merge}"
|
||||
Command="{Binding MergeCommand}"
|
||||
IsVisible="{Binding ShowMerge}"/>
|
||||
</StackPanel>
|
||||
</ctl:ModalShell.Footer>
|
||||
|
||||
<DockPanel>
|
||||
|
||||
<!-- Planning toolbar: combined-mode toggle + warning/loading -->
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="16,8,16,0"
|
||||
IsVisible="{Binding IsPlanning}">
|
||||
<ToggleButton Content="{loc:Tr planning.diff.previewCombined}" IsChecked="{Binding IsCombinedMode}"/>
|
||||
<TextBlock Text="{Binding CombinedWarning}"
|
||||
Foreground="{DynamicResource BloodBrush}"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding CombinedWarning, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||
<TextBlock Text="{loc:Tr planning.diff.loading}"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsLoadingCombined}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Body: nav | splitter | diff -->
|
||||
<Grid ColumnDefinitions="280,4,*" Margin="16">
|
||||
|
||||
<!-- Left nav: file tree (Files) OR subtask list (Planning) -->
|
||||
<Border Grid.Column="0" Classes="island">
|
||||
<DockPanel>
|
||||
<Border DockPanel.Dock="Top" Classes="island-header" IsVisible="{Binding !IsPlanning}">
|
||||
<TextBlock Classes="eyebrow" Text="{loc:Tr modals.diff.filesHeader}"/>
|
||||
</Border>
|
||||
<Panel>
|
||||
<!-- Files: folder tree -->
|
||||
<TreeView x:Name="FileTree"
|
||||
ItemsSource="{Binding FileTree}"
|
||||
SelectedItem="{Binding SelectedNode, Mode=TwoWay}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsVisible="{Binding !IsPlanning}">
|
||||
<TreeView.Styles>
|
||||
<Style Selector="TreeViewItem" x:DataType="vm:DiffTreeNodeViewModel">
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||
</Style>
|
||||
</TreeView.Styles>
|
||||
<TreeView.ItemTemplate>
|
||||
<TreeDataTemplate DataType="vm:DiffTreeNodeViewModel" ItemsSource="{Binding Children}">
|
||||
<Border Background="Transparent" Tapped="OnNodeTapped" Cursor="Hand" Padding="0,2">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<Border Grid.Column="0" Tag="{Binding StatusCode}" CornerRadius="3" Padding="4,0" Margin="0,0,6,0"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding StatusCode, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||
<TextBlock Text="{Binding StatusCode}"
|
||||
FontFamily="{DynamicResource MonoFont}" FontSize="{StaticResource FontSizeEyebrow}"
|
||||
Foreground="{DynamicResource TextBrush}"/>
|
||||
</Border>
|
||||
<TextBlock Grid.Column="1" Classes="meta" Text="{Binding Name}"
|
||||
VerticalAlignment="Center" TextTrimming="CharacterEllipsis"/>
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="4" VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowStats}">
|
||||
<TextBlock Foreground="{DynamicResource MossBrightBrush}" FontSize="{StaticResource FontSizeEyebrow}"
|
||||
Text="{Binding Additions, StringFormat='+{0}'}"/>
|
||||
<TextBlock Foreground="{DynamicResource BloodBrush}" FontSize="{StaticResource FontSizeEyebrow}"
|
||||
Text="{Binding Deletions, StringFormat='−{0}'}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</TreeDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
</TreeView>
|
||||
|
||||
<!-- Planning: subtask list -->
|
||||
<ListBox ItemsSource="{Binding Subtasks}"
|
||||
SelectedItem="{Binding SelectedSubtask}"
|
||||
IsEnabled="{Binding !IsCombinedMode}"
|
||||
IsVisible="{Binding IsPlanning}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SubtaskDiffRow">
|
||||
<Border Padding="10,8" Background="Transparent">
|
||||
<StackPanel Spacing="2">
|
||||
<TextBlock Classes="title" Text="{Binding Title}" TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock Classes="meta" Text="{Binding DiffStat}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Panel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<GridSplitter Grid.Column="1" ResizeDirection="Columns" Background="{DynamicResource LineBrush}"/>
|
||||
|
||||
<!-- Right: diff content (Files per-file pane OR Planning flat diff) -->
|
||||
<Border Grid.Column="2" Classes="island">
|
||||
<Panel>
|
||||
|
||||
<!-- Files mode: per-file pane with header + placeholders -->
|
||||
<DockPanel IsVisible="{Binding !IsPlanning}">
|
||||
<Border DockPanel.Dock="Top" Classes="island-header"
|
||||
IsVisible="{Binding SelectedFile, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<Border Grid.Column="0" Tag="{Binding SelectedFile.StatusCode}"
|
||||
CornerRadius="3" Padding="4,0" Margin="0,0,8,0" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding SelectedFile.StatusCode}"
|
||||
FontFamily="{DynamicResource MonoFont}" FontSize="{StaticResource FontSizeEyebrow}"
|
||||
Foreground="{DynamicResource TextBrush}"/>
|
||||
</Border>
|
||||
<TextBlock Grid.Column="1" Classes="path-mono" Text="{Binding SelectedFile.Path}"
|
||||
VerticalAlignment="Center" TextTrimming="PrefixCharacterEllipsis"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Grid Background="{DynamicResource VoidBrush}">
|
||||
<TextBlock Classes="body" Text="{Binding StatusMessage}"
|
||||
IsVisible="{Binding StatusMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<TextBlock Classes="body" Text="{loc:Tr modals.diff.binary}"
|
||||
Foreground="{DynamicResource TextMuteBrush}"
|
||||
IsVisible="{Binding SelectedFile.IsBinary}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<TextBlock Classes="body" Text="{loc:Tr modals.diff.empty}"
|
||||
Foreground="{DynamicResource TextMuteBrush}"
|
||||
IsVisible="{Binding SelectedFile.IsEmptyContent}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
|
||||
IsVisible="{Binding SelectedFile.HasLines}">
|
||||
<ctl:DiffLinesView Lines="{Binding SelectedFile.Lines}"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
|
||||
<!-- Planning mode: flat aggregate/combined diff -->
|
||||
<Grid Background="{DynamicResource VoidBrush}" IsVisible="{Binding IsPlanning}">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
||||
<ctl:DiffLinesView Lines="{Binding DiffLines}"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
</Panel>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</DockPanel>
|
||||
</ctl:ModalShell>
|
||||
</Window>
|
||||
41
src/ClaudeDo.Ui/Views/Modals/DiffViewerView.axaml.cs
Normal file
41
src/ClaudeDo.Ui/Views/Modals/DiffViewerView.axaml.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
|
||||
namespace ClaudeDo.Ui.Views.Modals;
|
||||
|
||||
public partial class DiffViewerView : Window
|
||||
{
|
||||
public DiffViewerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
base.OnDataContextChanged(e);
|
||||
if (DataContext is DiffViewerViewModel vm)
|
||||
vm.CloseAction = Close;
|
||||
|
||||
// SelectedItem TwoWay binding can miss on Avalonia 12 TreeView — back it
|
||||
// up with SelectionChanged.
|
||||
var tree = this.FindControl<TreeView>("FileTree");
|
||||
if (tree is not null)
|
||||
tree.SelectionChanged += OnFileTreeSelectionChanged;
|
||||
}
|
||||
|
||||
private void OnFileTreeSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (DataContext is DiffViewerViewModel vm && sender is TreeView tree)
|
||||
vm.SelectedNode = tree.SelectedItem as DiffTreeNodeViewModel;
|
||||
}
|
||||
|
||||
private void OnNodeTapped(object? sender, TappedEventArgs e)
|
||||
{
|
||||
if (sender is not Control c) return;
|
||||
if (c.DataContext is not DiffTreeNodeViewModel node) return;
|
||||
if (!node.IsDirectory) return;
|
||||
node.IsExpanded = !node.IsExpanded;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Modals"
|
||||
xmlns:converters="using:ClaudeDo.Ui.Converters"
|
||||
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"
|
||||
xmlns:loc="using:ClaudeDo.Ui.Localization"
|
||||
x:Class="ClaudeDo.Ui.Views.Modals.WorktreeModalView"
|
||||
x:DataType="vm:WorktreeModalViewModel"
|
||||
Title="{loc:Tr modals.worktree.title}"
|
||||
Width="1100" Height="720"
|
||||
MinWidth="640" MinHeight="400"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
WindowDecorations="BorderOnly"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
ExtendClientAreaTitleBarHeightHint="-1"
|
||||
Background="Transparent"
|
||||
CanResize="True"
|
||||
TransparencyLevelHint="AcrylicBlur">
|
||||
|
||||
<Window.KeyBindings>
|
||||
<KeyBinding Gesture="Escape" Command="{Binding CloseCommand}"/>
|
||||
</Window.KeyBindings>
|
||||
|
||||
<Border Classes="island" Margin="12">
|
||||
<DockPanel>
|
||||
|
||||
<!-- Title strip -->
|
||||
<Border DockPanel.Dock="Top" Height="36"
|
||||
Background="Transparent"
|
||||
PointerPressed="OnTitleBarPressed"
|
||||
PointerMoved="OnTitleBarMoved"
|
||||
PointerReleased="OnTitleBarReleased">
|
||||
<Grid ColumnDefinitions="*,Auto" Margin="14,0">
|
||||
<TextBlock Grid.Column="0" Text="{loc:Tr modals.worktree.title}" VerticalAlignment="Center"
|
||||
FontFamily="{DynamicResource MonoFont}" FontSize="{StaticResource FontSizeBody}"
|
||||
Foreground="{DynamicResource TextMuteBrush}"/>
|
||||
<Button Grid.Column="1" Classes="icon-btn" Content="✕"
|
||||
Command="{Binding CloseCommand}" VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Path strip -->
|
||||
<Border DockPanel.Dock="Top" Padding="14,0,14,8">
|
||||
<TextBlock Classes="path-mono" Text="{Binding WorktreePath}"/>
|
||||
</Border>
|
||||
|
||||
<!-- Split: file tree | splitter | diff pane -->
|
||||
<Grid ColumnDefinitions="260,4,*">
|
||||
|
||||
<!-- Left: file tree -->
|
||||
<TreeView x:Name="FileTree"
|
||||
Grid.Column="0"
|
||||
ItemsSource="{Binding Root}"
|
||||
SelectedItem="{Binding SelectedNode, Mode=TwoWay}"
|
||||
Background="Transparent"
|
||||
Margin="8,0,4,8">
|
||||
<TreeView.Styles>
|
||||
<Style Selector="TreeViewItem" x:DataType="vm:WorktreeNodeViewModel">
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||
</Style>
|
||||
</TreeView.Styles>
|
||||
<TreeView.ItemTemplate>
|
||||
<TreeDataTemplate DataType="vm:WorktreeNodeViewModel"
|
||||
ItemsSource="{Binding Children}">
|
||||
<Border Background="Transparent" Tapped="OnNodeTapped" Cursor="Hand">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Classes="meta" Text="{Binding Name}"/>
|
||||
<Border Tag="{Binding Status}" CornerRadius="3" Padding="4,0"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding Status, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<TextBlock Text="{Binding Status}"
|
||||
FontFamily="{DynamicResource MonoFont}" FontSize="{StaticResource FontSizeEyebrow}"
|
||||
Foreground="{DynamicResource TextBrush}"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</TreeDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
</TreeView>
|
||||
|
||||
<!-- Splitter -->
|
||||
<GridSplitter Grid.Column="1" ResizeDirection="Columns" Background="{DynamicResource LineBrush}"/>
|
||||
|
||||
<!-- Right: diff content -->
|
||||
<ScrollViewer Grid.Column="2" Padding="8"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Margin="4,0,8,8">
|
||||
<ctl:DiffLinesView Lines="{Binding SelectedFileDiffLines}"/>
|
||||
</ScrollViewer>
|
||||
|
||||
</Grid>
|
||||
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -1,96 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Animation.Easings;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.VisualTree;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
|
||||
namespace ClaudeDo.Ui.Views.Modals;
|
||||
|
||||
public partial class WorktreeModalView : Window
|
||||
{
|
||||
public WorktreeModalView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
base.OnDataContextChanged(e);
|
||||
if (DataContext is WorktreeModalViewModel vm)
|
||||
vm.CloseAction = Close;
|
||||
|
||||
// Wire TreeView selection — SelectedItem TwoWay binding may not fire
|
||||
// reliably in Avalonia 12 for TreeView; use SelectionChanged as backup.
|
||||
var tree = this.FindControl<TreeView>("FileTree");
|
||||
if (tree is not null)
|
||||
tree.SelectionChanged += OnFileTreeSelectionChanged;
|
||||
}
|
||||
|
||||
private void OnFileTreeSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (DataContext is WorktreeModalViewModel vm && sender is TreeView tree)
|
||||
vm.SelectedNode = tree.SelectedItem as WorktreeNodeViewModel;
|
||||
}
|
||||
|
||||
protected override async void OnOpened(EventArgs e)
|
||||
{
|
||||
base.OnOpened(e);
|
||||
Opacity = 0;
|
||||
RenderTransform = new ScaleTransform(0.98, 0.98);
|
||||
var anim = new Avalonia.Animation.Animation
|
||||
{
|
||||
Duration = TimeSpan.FromMilliseconds(180),
|
||||
Easing = new CubicEaseOut(),
|
||||
FillMode = FillMode.Forward,
|
||||
Children =
|
||||
{
|
||||
new KeyFrame { Cue = new Cue(0), Setters = { new Setter(OpacityProperty, 0d) } },
|
||||
new KeyFrame { Cue = new Cue(1), Setters = { new Setter(OpacityProperty, 1d) } },
|
||||
}
|
||||
};
|
||||
await anim.RunAsync(this);
|
||||
Opacity = 1;
|
||||
RenderTransform = new ScaleTransform(1.0, 1.0);
|
||||
}
|
||||
|
||||
private void OnNodeTapped(object? sender, Avalonia.Input.TappedEventArgs e)
|
||||
{
|
||||
if (sender is not Control c) return;
|
||||
if (c.DataContext is not WorktreeNodeViewModel node) return;
|
||||
if (!node.IsDirectory) return;
|
||||
node.IsExpanded = !node.IsExpanded;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private PixelPoint _dragStartScreen;
|
||||
private PixelPoint _dragStartPos;
|
||||
private bool _dragging;
|
||||
|
||||
private void OnTitleBarPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
|
||||
_dragStartScreen = this.PointToScreen(e.GetPosition(this));
|
||||
_dragStartPos = Position;
|
||||
_dragging = true;
|
||||
e.Pointer.Capture(sender as IInputElement);
|
||||
}
|
||||
|
||||
private void OnTitleBarMoved(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if (!_dragging || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
|
||||
var cur = this.PointToScreen(e.GetPosition(this));
|
||||
Position = new PixelPoint(
|
||||
_dragStartPos.X + (cur.X - _dragStartScreen.X),
|
||||
_dragStartPos.Y + (cur.Y - _dragStartScreen.Y));
|
||||
}
|
||||
|
||||
private void OnTitleBarReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
_dragging = false;
|
||||
e.Pointer.Capture(null);
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Planning"
|
||||
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"
|
||||
xmlns:loc="using:ClaudeDo.Ui.Localization"
|
||||
x:Class="ClaudeDo.Ui.Views.Planning.PlanningDiffView"
|
||||
x:DataType="vm:PlanningDiffViewModel"
|
||||
Title="{loc:Tr planning.diff.windowTitle}"
|
||||
Width="1100" Height="700" MinWidth="700" MinHeight="450"
|
||||
CanResize="True"
|
||||
WindowDecorations="BorderOnly"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
ExtendClientAreaTitleBarHeightHint="-1"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Background="{DynamicResource SurfaceBrush}">
|
||||
|
||||
<Window.KeyBindings>
|
||||
<KeyBinding Gesture="Escape" Command="{Binding CloseCommand}"/>
|
||||
</Window.KeyBindings>
|
||||
|
||||
<ctl:ModalShell Title="{loc:Tr planning.diff.modalTitle}" CloseCommand="{Binding CloseCommand}">
|
||||
|
||||
<!-- Toolbar row -->
|
||||
<DockPanel>
|
||||
<StackPanel DockPanel.Dock="Top"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8"
|
||||
Margin="8,6">
|
||||
<ToggleButton Content="{loc:Tr planning.diff.previewCombined}" IsChecked="{Binding IsCombinedMode}"/>
|
||||
<TextBlock Text="{Binding CombinedWarning}"
|
||||
Foreground="{DynamicResource BloodBrush}"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding CombinedWarning, Converter={x:Static ObjectConverters.IsNotNull}}"/>
|
||||
<TextBlock Text="{loc:Tr planning.diff.loading}"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsLoadingCombined}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Two-pane body -->
|
||||
<Grid ColumnDefinitions="240,*">
|
||||
|
||||
<!-- Subtask list (left pane) -->
|
||||
<Border Grid.Column="0"
|
||||
Classes="sidebar-pane">
|
||||
<ListBox ItemsSource="{Binding Subtasks}"
|
||||
SelectedItem="{Binding SelectedSubtask}"
|
||||
IsEnabled="{Binding !IsCombinedMode}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SubtaskDiffRow">
|
||||
<Border Padding="10,8" Background="Transparent">
|
||||
<StackPanel Spacing="2">
|
||||
<TextBlock Classes="title" Text="{Binding Title}"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
<TextBlock Classes="meta" Text="{Binding DiffStat}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
|
||||
<!-- Diff content (right pane) -->
|
||||
<Grid Grid.Column="1" Background="{DynamicResource VoidBrush}">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ctl:DiffLinesView Lines="{Binding DiffLines}"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
|
||||
</ctl:ModalShell>
|
||||
</Window>
|
||||
@@ -1,19 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using ClaudeDo.Ui.ViewModels.Planning;
|
||||
|
||||
namespace ClaudeDo.Ui.Views.Planning;
|
||||
|
||||
public partial class PlanningDiffView : Window
|
||||
{
|
||||
public PlanningDiffView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
base.OnDataContextChanged(e);
|
||||
if (DataContext is PlanningDiffViewModel vm)
|
||||
vm.CloseAction = Close;
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ public sealed class WindowDialogService : IDialogService
|
||||
};
|
||||
vm.ShowDiffAction = diffVm =>
|
||||
{
|
||||
var diffDlg = new WorktreeModalView { DataContext = diffVm };
|
||||
var diffDlg = new DiffViewerView { DataContext = diffVm };
|
||||
diffVm.CloseAction = () => diffDlg.Close();
|
||||
_ = diffVm.LoadAsync();
|
||||
_ = diffDlg.ShowDialog(_owner);
|
||||
|
||||
Reference in New Issue
Block a user