Skip to content

Commit cd0dabd

Browse files
Merge pull request #46 from erikdarlingdata/dev
Release v0.9.0
2 parents d7432a7 + 72faa93 commit cd0dabd

13 files changed

Lines changed: 735 additions & 51 deletions

src/PlanViewer.App/AboutWindow.axaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
33
x:Class="PlanViewer.App.AboutWindow"
44
Title="About Performance Studio"
5-
Width="450" Height="420"
5+
Width="450" Height="460"
66
CanResize="False"
77
WindowStartupLocation="CenterOwner"
88
Icon="avares://PlanViewer.App/EDD.ico"
@@ -63,6 +63,12 @@
6363
<TextBlock Text="Restart the application after changing MCP settings."
6464
FontSize="11" Foreground="{DynamicResource ForegroundMutedBrush}"
6565
Margin="0,4,0,0"/>
66+
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,8,0,0">
67+
<Button x:Name="CopyMcpCommandButton" Content="Copy MCP Command"
68+
Click="CopyMcpCommand_Click" Padding="10,4" FontSize="12"/>
69+
<TextBlock x:Name="McpCopyStatus" FontSize="11" VerticalAlignment="Center"
70+
Foreground="{DynamicResource ForegroundMutedBrush}"/>
71+
</StackPanel>
6672
</StackPanel>
6773

6874
<!-- Close -->

src/PlanViewer.App/AboutWindow.axaml.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Text.Json;
1313
using Avalonia.Controls;
1414
using Avalonia.Input;
15+
using Avalonia.Input.Platform;
1516
using Avalonia.Interactivity;
1617
using PlanViewer.App.Mcp;
1718

@@ -59,6 +60,18 @@ private void SaveMcpSettings()
5960
private void GitHubLink_Click(object? sender, PointerPressedEventArgs e) => OpenUrl(GitHubUrl);
6061
private void ReportIssueLink_Click(object? sender, PointerPressedEventArgs e) => OpenUrl(IssuesUrl);
6162
private void DarlingDataLink_Click(object? sender, PointerPressedEventArgs e) => OpenUrl(DarlingDataUrl);
63+
private async void CopyMcpCommand_Click(object? sender, RoutedEventArgs e)
64+
{
65+
var port = int.TryParse(McpPortInput.Text, out var p) && p >= 1024 && p <= 65535 ? p : 5152;
66+
var command = $"claude mcp add --transport streamable-http --scope user performance-studio http://localhost:{port}/";
67+
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
68+
if (clipboard != null)
69+
{
70+
await clipboard.SetTextAsync(command);
71+
McpCopyStatus.Text = "Copied to clipboard!";
72+
}
73+
}
74+
6275
private void CloseButton_Click(object? sender, RoutedEventArgs e) => Close();
6376

6477
private static void OpenUrl(string url)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<UserControl xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
x:Class="PlanViewer.App.Controls.ColumnFilterPopup"
4+
Width="220">
5+
<Border Background="{DynamicResource BackgroundDarkBrush}"
6+
BorderBrush="{DynamicResource BorderBrush}"
7+
BorderThickness="1" CornerRadius="6">
8+
<StackPanel Margin="12" Spacing="4">
9+
<TextBlock x:Name="HeaderText" Text="Filter Column"
10+
FontWeight="SemiBold" FontSize="13" Margin="0,0,0,8"
11+
Foreground="{DynamicResource ForegroundBrush}"/>
12+
<TextBlock Text="Operator:" FontSize="11" Margin="0,0,0,2"
13+
Foreground="{DynamicResource ForegroundBrush}"/>
14+
<ComboBox x:Name="OperatorComboBox" HorizontalAlignment="Stretch"
15+
Height="28" FontSize="12" Margin="0,0,0,8"
16+
SelectionChanged="OperatorComboBox_SelectionChanged"/>
17+
<TextBlock x:Name="ValueLabel" Text="Value:" FontSize="11" Margin="0,0,0,2"
18+
Foreground="{DynamicResource ForegroundBrush}"/>
19+
<TextBox x:Name="ValueTextBox" HorizontalAlignment="Stretch"
20+
Height="28" FontSize="12" Margin="0,0,0,12"
21+
KeyDown="ValueTextBox_KeyDown"/>
22+
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
23+
<Button Content="Clear" Width="65" Height="28"
24+
Click="ClearButton_Click"
25+
Theme="{StaticResource AppButton}"/>
26+
<Button Content="Apply" Width="65" Height="28"
27+
Click="ApplyButton_Click"
28+
Theme="{StaticResource AppButton}"/>
29+
</StackPanel>
30+
</StackPanel>
31+
</Border>
32+
</UserControl>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using System;
2+
using Avalonia.Controls;
3+
using Avalonia.Input;
4+
using Avalonia.Interactivity;
5+
6+
namespace PlanViewer.App.Controls;
7+
8+
public partial class ColumnFilterPopup : UserControl
9+
{
10+
public event EventHandler<FilterAppliedEventArgs>? FilterApplied;
11+
public event EventHandler? FilterCleared;
12+
13+
private string _currentColumnName = "";
14+
15+
private static readonly (string Display, FilterOperator Op)[] Operators =
16+
[
17+
("Contains", FilterOperator.Contains),
18+
("Equals (=)", FilterOperator.Equals),
19+
("Not Equals (!=)", FilterOperator.NotEquals),
20+
("Starts With", FilterOperator.StartsWith),
21+
("Ends With", FilterOperator.EndsWith),
22+
("Greater Than (>)", FilterOperator.GreaterThan),
23+
("Greater or Equal (>=)", FilterOperator.GreaterThanOrEqual),
24+
("Less Than (<)", FilterOperator.LessThan),
25+
("Less or Equal (<=)", FilterOperator.LessThanOrEqual),
26+
("Is Empty", FilterOperator.IsEmpty),
27+
("Is Not Empty", FilterOperator.IsNotEmpty),
28+
];
29+
30+
public ColumnFilterPopup()
31+
{
32+
InitializeComponent();
33+
foreach (var (display, _) in Operators)
34+
OperatorComboBox.Items.Add(display);
35+
OperatorComboBox.SelectedIndex = 0;
36+
}
37+
38+
public void Initialize(string columnName, ColumnFilterState? existingFilter)
39+
{
40+
_currentColumnName = columnName;
41+
HeaderText.Text = $"Filter: {columnName}";
42+
43+
if (existingFilter?.IsActive == true)
44+
{
45+
var idx = Array.FindIndex(Operators, o => o.Op == existingFilter.Operator);
46+
OperatorComboBox.SelectedIndex = idx >= 0 ? idx : 0;
47+
ValueTextBox.Text = existingFilter.Value;
48+
}
49+
else
50+
{
51+
OperatorComboBox.SelectedIndex = 0;
52+
ValueTextBox.Text = "";
53+
}
54+
55+
UpdateValueVisibility();
56+
ValueTextBox.Focus();
57+
}
58+
59+
private void UpdateValueVisibility()
60+
{
61+
var idx = OperatorComboBox.SelectedIndex;
62+
var op = (idx >= 0 && idx < Operators.Length) ? Operators[idx].Op : FilterOperator.Contains;
63+
var showValue = op != FilterOperator.IsEmpty && op != FilterOperator.IsNotEmpty;
64+
ValueLabel.IsVisible = showValue;
65+
ValueTextBox.IsVisible = showValue;
66+
}
67+
68+
private void OperatorComboBox_SelectionChanged(object? sender, SelectionChangedEventArgs e)
69+
{
70+
UpdateValueVisibility();
71+
}
72+
73+
private void ApplyFilter()
74+
{
75+
var idx = OperatorComboBox.SelectedIndex;
76+
if (idx < 0 || idx >= Operators.Length) return;
77+
78+
FilterApplied?.Invoke(this, new FilterAppliedEventArgs
79+
{
80+
FilterState = new ColumnFilterState
81+
{
82+
ColumnName = _currentColumnName,
83+
Operator = Operators[idx].Op,
84+
Value = ValueTextBox.Text ?? "",
85+
}
86+
});
87+
}
88+
89+
private void ApplyButton_Click(object? sender, RoutedEventArgs e) => ApplyFilter();
90+
91+
private void ClearButton_Click(object? sender, RoutedEventArgs e)
92+
{
93+
FilterApplied?.Invoke(this, new FilterAppliedEventArgs
94+
{
95+
FilterState = new ColumnFilterState { ColumnName = _currentColumnName }
96+
});
97+
FilterCleared?.Invoke(this, EventArgs.Empty);
98+
}
99+
100+
private void ValueTextBox_KeyDown(object? sender, KeyEventArgs e)
101+
{
102+
if (e.Key == Key.Enter)
103+
{
104+
ApplyFilter();
105+
e.Handled = true;
106+
}
107+
else if (e.Key == Key.Escape)
108+
{
109+
FilterCleared?.Invoke(this, EventArgs.Empty);
110+
e.Handled = true;
111+
}
112+
}
113+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System;
2+
3+
namespace PlanViewer.App.Controls;
4+
5+
public enum FilterOperator
6+
{
7+
Contains,
8+
Equals,
9+
NotEquals,
10+
StartsWith,
11+
EndsWith,
12+
GreaterThan,
13+
GreaterThanOrEqual,
14+
LessThan,
15+
LessThanOrEqual,
16+
IsEmpty,
17+
IsNotEmpty,
18+
}
19+
20+
public class ColumnFilterState
21+
{
22+
public string ColumnName { get; set; } = string.Empty;
23+
public FilterOperator Operator { get; set; } = FilterOperator.Contains;
24+
public string Value { get; set; } = string.Empty;
25+
26+
public bool IsActive =>
27+
!string.IsNullOrEmpty(Value) ||
28+
Operator == FilterOperator.IsEmpty ||
29+
Operator == FilterOperator.IsNotEmpty;
30+
31+
public string DisplayText
32+
{
33+
get
34+
{
35+
if (!IsActive) return string.Empty;
36+
37+
return Operator switch
38+
{
39+
FilterOperator.Contains => $"Contains '{Value}'",
40+
FilterOperator.Equals => $"= '{Value}'",
41+
FilterOperator.NotEquals => $"!= '{Value}'",
42+
FilterOperator.GreaterThan => $"> {Value}",
43+
FilterOperator.GreaterThanOrEqual => $">= {Value}",
44+
FilterOperator.LessThan => $"< {Value}",
45+
FilterOperator.LessThanOrEqual => $"<= {Value}",
46+
FilterOperator.StartsWith => $"Starts with '{Value}'",
47+
FilterOperator.EndsWith => $"Ends with '{Value}'",
48+
FilterOperator.IsEmpty => "Is Empty",
49+
FilterOperator.IsNotEmpty => "Is Not Empty",
50+
_ => Value,
51+
};
52+
}
53+
}
54+
}
55+
56+
public class FilterAppliedEventArgs : EventArgs
57+
{
58+
public ColumnFilterState FilterState { get; set; } = new ColumnFilterState();
59+
}

src/PlanViewer.App/Controls/PlanViewerControl.axaml

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,33 @@
6060
</Expander.Header>
6161
<Grid MaxHeight="220">
6262
<Grid.ColumnDefinitions>
63+
<ColumnDefinition Width="Auto" MinWidth="0"/>
6364
<ColumnDefinition Width="Auto" MinWidth="180"/>
6465
<ColumnDefinition Width="*" MinWidth="200"/>
6566
<ColumnDefinition Width="*" MinWidth="200"/>
6667
<ColumnDefinition Width="*" MinWidth="200"/>
6768
</Grid.ColumnDefinitions>
6869

69-
<!-- Runtime Summary (left) -->
70-
<Border Grid.Column="0" Padding="10,4,10,8"
70+
<!-- Server Context (first, only visible when connected) -->
71+
<Border x:Name="ServerContextBorder" Grid.Column="0" Padding="10,4,10,8"
72+
Background="#1A1A2D"
73+
BorderBrush="#3A3A5A" BorderThickness="0,0,1,0"
74+
IsVisible="False">
75+
<ScrollViewer VerticalScrollBarVisibility="Auto"
76+
HorizontalScrollBarVisibility="Disabled">
77+
<StackPanel>
78+
<TextBlock Text="Server Context"
79+
FontSize="13"
80+
FontWeight="SemiBold"
81+
Foreground="#9B9BFF"
82+
Margin="0,0,0,6"/>
83+
<StackPanel x:Name="ServerContextContent"/>
84+
</StackPanel>
85+
</ScrollViewer>
86+
</Border>
87+
88+
<!-- Runtime Summary -->
89+
<Border Grid.Column="1" Padding="10,4,10,8"
7190
Background="{DynamicResource BackgroundDarkBrush}"
7291
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,1,0">
7392
<StackPanel>
@@ -81,7 +100,7 @@
81100
</Border>
82101

83102
<!-- Missing Indexes (center) -->
84-
<Border Grid.Column="1" Padding="10,4,10,8"
103+
<Border Grid.Column="2" Padding="10,4,10,8"
85104
Background="#3D2A0E"
86105
BorderBrush="#7A5A1E" BorderThickness="0,0,1,0">
87106
<ScrollViewer VerticalScrollBarVisibility="Auto"
@@ -102,7 +121,7 @@
102121
</Border>
103122

104123
<!-- Parameters -->
105-
<Border Grid.Column="2" Padding="10,4,10,8"
124+
<Border Grid.Column="3" Padding="10,4,10,8"
106125
Background="#1A2D1A"
107126
BorderBrush="#3A5A3A" BorderThickness="0,0,1,0">
108127
<ScrollViewer VerticalScrollBarVisibility="Auto"
@@ -123,7 +142,7 @@
123142
</Border>
124143

125144
<!-- Wait Stats (right, fills remaining space) -->
126-
<Border Grid.Column="3" Padding="10,4,10,8"
145+
<Border Grid.Column="4" Padding="10,4,10,8"
127146
Background="#1A2A3D">
128147
<ScrollViewer VerticalScrollBarVisibility="Auto"
129148
HorizontalScrollBarVisibility="Disabled">

0 commit comments

Comments
 (0)