feat(mission-control): column picker for the overview grid
Auto keeps the pane-count heuristic; 1-4 fixes the column count (capped at the pane count). Persisted to ui.config.json as MissionControlColumns.
This commit is contained in:
@@ -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}">
|
||||
|
||||
Reference in New Issue
Block a user