chore(claude-do): merge Customizable Spalten Anzahl in MIssion controll
ClaudeDo-Task: 99bfb985efb64c02ba3f27b53eae790d
This commit is contained in:
@@ -214,7 +214,8 @@ sealed class Program
|
||||
new MissionControlViewModel(
|
||||
sp.GetRequiredService<IDbContextFactory<ClaudeDoDbContext>>(),
|
||||
sp.GetRequiredService<IWorkerClient>(),
|
||||
sp.GetRequiredService<UsagePillViewModel>()));
|
||||
sp.GetRequiredService<UsagePillViewModel>(),
|
||||
sp.GetRequiredService<AppSettings>()));
|
||||
sc.AddSingleton<IslandsShellViewModel>(sp =>
|
||||
{
|
||||
var shell = ActivatorUtilities.CreateInstance<IslandsShellViewModel>(sp);
|
||||
|
||||
@@ -386,6 +386,8 @@
|
||||
"briefOpenFailed": "Brief konnte nicht geöffnet werden: {0}",
|
||||
"focusMode": "Fokus",
|
||||
"overviewMode": "Übersicht",
|
||||
"columns": "Spalten",
|
||||
"columnsAuto": "Auto",
|
||||
"closeSession": "Sitzung hart beenden",
|
||||
"closeSessionConfirm": "Laufende Sitzung beenden? Der Claude-Prozess wird abgebrochen.",
|
||||
"conptyLaunchFailed": "Interaktive Sitzung konnte nicht geöffnet werden: {0}",
|
||||
|
||||
@@ -386,6 +386,8 @@
|
||||
"briefOpenFailed": "Couldn't open the brief: {0}",
|
||||
"focusMode": "Focus",
|
||||
"overviewMode": "Overview",
|
||||
"columns": "Columns",
|
||||
"columnsAuto": "Auto",
|
||||
"closeSession": "Kill session",
|
||||
"closeSessionConfirm": "End the running session? The Claude process will be aborted.",
|
||||
"conptyLaunchFailed": "Couldn't open interactive session: {0}",
|
||||
|
||||
@@ -16,6 +16,9 @@ public sealed class AppSettings
|
||||
public string DiffViewMode { get; set; } = "unified";
|
||||
public bool DiffWrapLines { get; set; }
|
||||
|
||||
/// <summary>Mission Control overview column count; 0 = auto (derived from the pane count).</summary>
|
||||
public int MissionControlColumns { get; set; }
|
||||
|
||||
private static readonly string DefaultConfigPath = Paths.Expand("~/.claudeDo/ui.config.json");
|
||||
|
||||
/// Where this instance persists. Instance-level (not static) so tests can redirect it —
|
||||
|
||||
@@ -19,6 +19,14 @@ public sealed class HexBrushConverter : OneWayConverter
|
||||
: Avalonia.Application.Current?.TryGetResource(parameter as string ?? "", null, out var res) == true ? res : null;
|
||||
}
|
||||
|
||||
/// <summary>True when the value's string form equals the parameter (segmented-switch active state).</summary>
|
||||
public sealed class EqualsParameterConverter : OneWayConverter
|
||||
{
|
||||
public static EqualsParameterConverter Instance { get; } = new();
|
||||
public override object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> Equals(value?.ToString(), parameter as string);
|
||||
}
|
||||
|
||||
public sealed class UpperCaseConverter : OneWayConverter
|
||||
{
|
||||
public static UpperCaseConverter Instance { get; } = new();
|
||||
|
||||
@@ -18,6 +18,7 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||
private readonly IWorkerClient _worker;
|
||||
private readonly AppSettings? _settings;
|
||||
private readonly Action<string, string, DateTime> _onTaskStarted;
|
||||
private readonly Action<string, string, string, DateTime> _onTaskFinished;
|
||||
private readonly Action<string> _onTaskUpdated;
|
||||
@@ -44,6 +45,9 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
|
||||
[ObservableProperty] private int _columnCount = 1;
|
||||
|
||||
/// <summary>User-picked column count for the overview grid; 0 = auto (derived from the pane count).</summary>
|
||||
[ObservableProperty] private int _columnPreference;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(LayoutToggleLabel))]
|
||||
private bool _isFocusMode;
|
||||
@@ -94,11 +98,13 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
|
||||
public UsagePillViewModel UsagePill { get; }
|
||||
|
||||
public MissionControlViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory, IWorkerClient worker, UsagePillViewModel usagePill)
|
||||
public MissionControlViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory, IWorkerClient worker, UsagePillViewModel usagePill, AppSettings? settings = null)
|
||||
{
|
||||
_dbFactory = dbFactory;
|
||||
_worker = worker;
|
||||
UsagePill = usagePill;
|
||||
_settings = settings;
|
||||
_columnPreference = Math.Clamp(settings?.MissionControlColumns ?? 0, 0, MaxColumns);
|
||||
|
||||
ConPtySessions.CollectionChanged += OnConPtySessionsChanged;
|
||||
Panes.CollectionChanged += OnPanesChanged;
|
||||
@@ -334,6 +340,32 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
[RelayCommand]
|
||||
private void ToggleLayout() => IsFocusMode = !IsFocusMode;
|
||||
|
||||
/// <summary>Upper bound of the column picker.</summary>
|
||||
public const int MaxColumns = 4;
|
||||
|
||||
/// <summary>Sets the column preference from the header picker ("0" = auto) and persists it.</summary>
|
||||
[RelayCommand]
|
||||
private void SetColumns(string value) => ColumnPreference = Math.Clamp(int.Parse(value), 0, MaxColumns);
|
||||
|
||||
partial void OnColumnPreferenceChanged(int value)
|
||||
{
|
||||
UpdateColumnCount();
|
||||
if (_settings is null) return;
|
||||
_settings.MissionControlColumns = value;
|
||||
try { _settings.Save(); }
|
||||
catch (Exception ex) { ErrorReported?.Invoke(ex.Message); }
|
||||
}
|
||||
|
||||
// Never more columns than panes, so a wide preference doesn't leave empty cells.
|
||||
private void UpdateColumnCount() => ColumnCount = ColumnPreference > 0
|
||||
? Math.Max(1, Math.Min(ColumnPreference, Panes.Count))
|
||||
: Panes.Count switch
|
||||
{
|
||||
<= 1 => 1,
|
||||
<= 4 => 2,
|
||||
_ => 3,
|
||||
};
|
||||
|
||||
// Fetches the launch spec for a task's worktree and hosts an embedded ConPTY session as a
|
||||
// Command Center pane (task-based only).
|
||||
public async System.Threading.Tasks.Task OpenConPtySessionAsync(string taskId)
|
||||
@@ -716,12 +748,7 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
|
||||
private void OnPanesChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
ColumnCount = Panes.Count switch
|
||||
{
|
||||
<= 1 => 1,
|
||||
<= 4 => 2,
|
||||
_ => 3,
|
||||
};
|
||||
UpdateColumnCount();
|
||||
OnPropertyChanged(nameof(HasPanes));
|
||||
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems?[0] is ConPtyPaneViewModel added)
|
||||
FocusedPane = added;
|
||||
|
||||
@@ -102,6 +102,27 @@
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<Border Classes="segmented" VerticalAlignment="Center"
|
||||
IsVisible="{Binding !IsFocusMode}"
|
||||
ToolTip.Tip="{loc:Tr missionControl.columns}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Classes="segment" Content="{loc:Tr missionControl.columnsAuto}"
|
||||
Classes.active="{Binding ColumnPreference, Converter={x:Static conv:EqualsParameterConverter.Instance}, ConverterParameter=0}"
|
||||
Command="{Binding SetColumnsCommand}" CommandParameter="0"/>
|
||||
<Button Classes="segment" Content="1"
|
||||
Classes.active="{Binding ColumnPreference, Converter={x:Static conv:EqualsParameterConverter.Instance}, ConverterParameter=1}"
|
||||
Command="{Binding SetColumnsCommand}" CommandParameter="1"/>
|
||||
<Button Classes="segment" Content="2"
|
||||
Classes.active="{Binding ColumnPreference, Converter={x:Static conv:EqualsParameterConverter.Instance}, ConverterParameter=2}"
|
||||
Command="{Binding SetColumnsCommand}" CommandParameter="2"/>
|
||||
<Button Classes="segment" Content="3"
|
||||
Classes.active="{Binding ColumnPreference, Converter={x:Static conv:EqualsParameterConverter.Instance}, ConverterParameter=3}"
|
||||
Command="{Binding SetColumnsCommand}" CommandParameter="3"/>
|
||||
<Button Classes="segment" Content="4"
|
||||
Classes.active="{Binding ColumnPreference, Converter={x:Static conv:EqualsParameterConverter.Instance}, ConverterParameter=4}"
|
||||
Command="{Binding SetColumnsCommand}" CommandParameter="4"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Button Classes="icon-btn"
|
||||
Command="{Binding ToggleLayoutCommand}"
|
||||
ToolTip.Tip="{Binding LayoutToggleLabel}">
|
||||
|
||||
@@ -491,6 +491,33 @@ public class MissionControlViewModelTests : IDisposable
|
||||
Assert.Equal(2, vm.ColumnCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetColumns_OverridesAutoCapsAtPaneCountAndPersists()
|
||||
{
|
||||
var path = Path.Combine(Path.GetTempPath(), $"claudedo_mc_cfg_{Guid.NewGuid():N}.json");
|
||||
try
|
||||
{
|
||||
var worker = new FakeWorker();
|
||||
using var vm = new MissionControlViewModel(new TestDbFactory(NewContext), worker,
|
||||
new UsagePillViewModel(worker), new AppSettings { ConfigPath = path });
|
||||
for (var i = 1; i <= 4; i++) await vm.OpenConPtySessionAsync($"t{i}");
|
||||
Assert.Equal(2, vm.ColumnCount); // auto: 4 panes -> 2x2
|
||||
|
||||
vm.SetColumnsCommand.Execute("3");
|
||||
Assert.Equal(3, vm.ColumnCount);
|
||||
|
||||
vm.SetColumnsCommand.Execute("4");
|
||||
vm.Panes[0].CloseCommand.Execute(null);
|
||||
Assert.Equal(3, vm.ColumnCount); // never more columns than panes
|
||||
|
||||
Assert.Equal(4, AppSettings.Load(path).MissionControlColumns);
|
||||
|
||||
vm.SetColumnsCommand.Execute("0");
|
||||
Assert.Equal(2, vm.ColumnCount);
|
||||
}
|
||||
finally { try { File.Delete(path); } catch { } }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CloseConPtySession_RemovesFromConPtySessionsAndPanes()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user