feat(ui): Mission Control als Session-Hub
- "+" öffnet ein Menü mit allen Listen (working dir) statt direkt den Ordner-Dialog; "Ordner wählen…" bleibt als Fallback erste Zeile, damit das Menü auch bei fehlgeschlagener Listen-Abfrage nutzbar bleibt - Rail-Rows: Kontextmenü "Sitzung öffnen" (gesperrt für Running — der Worktree gehört dort dem autonomen Lauf) - Aktivitäts-Strip statt reiner Queue: WaitingForReview/Failed/Roadblock kommen dazu, Attention zuerst, unterschieden nur über Farbe (Aktionen bleiben in der Task-Detailansicht)
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
xmlns:vmm="using:ClaudeDo.Ui.ViewModels.MissionControl"
|
||||
xmlns:mc="using:ClaudeDo.Ui.Views.MissionControl"
|
||||
xmlns:controls="using:ClaudeDo.Ui.Views.Controls"
|
||||
xmlns:conv="using:ClaudeDo.Ui.Converters"
|
||||
xmlns:loc="using:ClaudeDo.Ui.Localization"
|
||||
x:DataType="vm:MissionControlViewModel"
|
||||
x:Class="ClaudeDo.Ui.Views.MissionControl.MissionControlView">
|
||||
@@ -33,9 +34,19 @@
|
||||
Margin="0,0,4,0"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button Classes="icon-btn"
|
||||
Click="OnNewSessionClicked"
|
||||
ToolTip.Tip="{loc:Tr missionControl.newSession}">
|
||||
<PathIcon Data="{StaticResource Icon.Plus}" Width="15" Height="15"/>
|
||||
<Button.Flyout>
|
||||
<MenuFlyout ItemsSource="{Binding SessionTargets}" Opening="OnSessionFlyoutOpening">
|
||||
<MenuFlyout.ItemContainerTheme>
|
||||
<ControlTheme TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}"
|
||||
x:DataType="vm:SessionTargetViewModel">
|
||||
<Setter Property="Header" Value="{Binding Label}"/>
|
||||
<Setter Property="Command" Value="{Binding StartCommand}"/>
|
||||
</ControlTheme>
|
||||
</MenuFlyout.ItemContainerTheme>
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<Button Classes="icon-btn"
|
||||
Command="{Binding ToggleLayoutCommand}"
|
||||
@@ -100,7 +111,7 @@
|
||||
BorderThickness="1,0,0,0">
|
||||
<DockPanel LastChildFill="True" Margin="10,10">
|
||||
<TextBlock DockPanel.Dock="Top" Classes="eyebrow"
|
||||
Text="{loc:Tr missionControl.queue}"
|
||||
Text="{loc:Tr missionControl.activity}"
|
||||
Foreground="{DynamicResource TextMuteBrush}"
|
||||
LetterSpacing="1.4" Margin="0,0,0,8" />
|
||||
<ScrollViewer>
|
||||
@@ -112,27 +123,31 @@
|
||||
Background="Transparent" BorderThickness="0"
|
||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
|
||||
Cursor="Hand">
|
||||
<Button.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{loc:Tr missionControl.openSession}"
|
||||
Command="{Binding OpenSessionCommand}"
|
||||
IsEnabled="{Binding CanOpenSession}" />
|
||||
</ContextMenu>
|
||||
</Button.ContextMenu>
|
||||
<Panel>
|
||||
<Border Background="{DynamicResource SurfaceBrush}"
|
||||
BorderBrush="{DynamicResource LineBrush}"
|
||||
BorderThickness="1" CornerRadius="6" />
|
||||
<Border Background="{DynamicResource RunningTintBrush}"
|
||||
BorderBrush="{DynamicResource RunningTintBorderBrush}"
|
||||
<!-- Single state tint; brush keys come from the VM (Tokens.axaml). -->
|
||||
<Border Background="{Binding TintKey, Converter={x:Static conv:DotBrushConverter.Instance}}"
|
||||
BorderBrush="{Binding TintBorderKey, Converter={x:Static conv:DotBrushConverter.Instance}}"
|
||||
BorderThickness="1" CornerRadius="6"
|
||||
IsVisible="{Binding IsRunning}" />
|
||||
IsVisible="{Binding TintKey, Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||
<StackPanel Margin="8,6" Spacing="2">
|
||||
<TextBlock Text="{Binding Title}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
ToolTip.Tip="{Binding Title}"
|
||||
Foreground="{DynamicResource TextDimBrush}" />
|
||||
<TextBlock Classes="meta"
|
||||
Text="{loc:Tr missionControl.blocked}"
|
||||
IsVisible="{Binding IsBlocked}"
|
||||
Foreground="{DynamicResource AmberBrush}" />
|
||||
<TextBlock Classes="meta"
|
||||
Text="{loc:Tr missionControl.running}"
|
||||
IsVisible="{Binding IsRunning}"
|
||||
Foreground="{DynamicResource StatusRunningBrush}" />
|
||||
Text="{Binding StatusLabel}"
|
||||
IsVisible="{Binding StatusLabel, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
Foreground="{Binding StatusColorKey, Converter={x:Static conv:DotBrushConverter.Instance}}" />
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Button>
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
@@ -30,8 +29,10 @@ public partial class MissionControlView : UserControl
|
||||
QueueSplitter.DragCompleted += (_, _) => _queueWidth = BodyGrid.ColumnDefinitions[2].Width;
|
||||
}
|
||||
|
||||
private void OnSessionFlyoutOpening(object? sender, EventArgs e) => _ = _vm?.RefreshSessionTargetsAsync();
|
||||
|
||||
// Ad-hoc ConPTY session: the view owns the folder picker, the VM only takes the chosen path.
|
||||
private async void OnNewSessionClicked(object? sender, RoutedEventArgs e)
|
||||
private async void BrowseForSessionAsync()
|
||||
{
|
||||
if (DataContext is not MissionControlViewModel vm) return;
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
@@ -53,6 +54,7 @@ public partial class MissionControlView : UserControl
|
||||
{
|
||||
_vm.Panes.CollectionChanged -= OnPanesChanged;
|
||||
_vm.PropertyChanged -= OnViewModelPropertyChanged;
|
||||
_vm.BrowseForSessionRequested = null;
|
||||
}
|
||||
|
||||
_vm = DataContext as MissionControlViewModel;
|
||||
@@ -61,6 +63,7 @@ public partial class MissionControlView : UserControl
|
||||
{
|
||||
_vm.Panes.CollectionChanged += OnPanesChanged;
|
||||
_vm.PropertyChanged += OnViewModelPropertyChanged;
|
||||
_vm.BrowseForSessionRequested = BrowseForSessionAsync;
|
||||
}
|
||||
|
||||
RebuildOverviewGrid();
|
||||
|
||||
Reference in New Issue
Block a user