Skip to content

Navigation Menu

Sign in
Sign up

P2: compact FAT header hierarchy #264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
masarray wants to merge 5 commits into fix/p1-fat-v2-first-paint-schema
base: fix/p1-fat-v2-first-paint-schema
Choose a base branch
Loading
from fix/p2-fat-compact-header
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 14 additions & 17 deletions IoListTestingWindow.ClockSyncUx.cs
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ private void ClockSyncUx_Loaded(object sender, RoutedEventArgs e)
Margin = new Thickness(0, 0, 8, 0),
Padding = new Thickness(2, 0, 2, 0),
FontSize = 11.2,
FontWeight = FontWeights.SemiBold,
FontWeight = FontWeights.Medium,
Foreground = TryFindResource("Ink") as Brush ?? Brushes.DimGray,
Text = "Global SNTP",
Text = "SNTP · —",
ToolTip = "Global SNTP is controlled from the ARSAS header. It is not owned by FAT and continues running when the FAT window closes while the global server toggle remains enabled."
};

Expand All @@ -54,7 +54,7 @@ private void ClockSyncUx_Loaded(object sender, RoutedEventArgs e)
FontSize = 10.4,
FontWeight = FontWeights.Medium,
Foreground = TryFindResource("MutedInk") as Brush ?? Brushes.SlateGray,
Text = "SNTP: waiting"
Text = "Clock · waiting"
};

_clockSyncGlobalStatusText = globalStatus;
Expand Down Expand Up @@ -104,25 +104,22 @@ private void RefreshClockSyncEvidence(SntpClockServiceSnapshot snapshot)
SntpClockTransportMode.UdpSocket => "UDP",
_ => "—"
};
var localAddress = snapshot.Binding?.LocalAddress.ToString();

_clockSyncGlobalStatusText.Text = !enabled
? "Global SNTP · Off"
: snapshot.State == SntpClockServiceState.Serving
? $"Global SNTP · {localAddress ?? "Active"}"
: "Global SNTP · Enabled";
_clockSyncGlobalStatusText.Text = enabled
? "SNTP · ON"
: "SNTP · OFF";

_clockSyncEvidenceText.Text = !enabled
? "SNTP: off"
? "Clock · idle"
: snapshot.State switch
{
SntpClockServiceState.Serving =>
$"{transport} · B {snapshot.BroadcastCount} · Req {snapshot.ClientRequestCount} · Reply {snapshot.ReplyCount} · sync not proven",
SntpClockServiceState.Starting => "SNTP: starting...",
SntpClockServiceState.Stopped => "SNTP: waiting for connected IED",
SntpClockServiceState.PortUnavailable => "SNTP: unavailable",
SntpClockServiceState.Faulted => "SNTP: fault",
_ => $"SNTP: {snapshot.State}"
$"{transport} · B {snapshot.BroadcastCount} · Req {snapshot.ClientRequestCount} · Rep {snapshot.ReplyCount}",
SntpClockServiceState.Starting => "Clock · starting...",
SntpClockServiceState.Stopped => "Clock · waiting",
SntpClockServiceState.PortUnavailable => "Clock · unavailable",
SntpClockServiceState.Faulted => "Clock · fault",
_ => $"Clock · {snapshot.State}"
};

var toolTip = BuildClockSyncEvidenceToolTip(snapshot, transport, enabled);
Expand Down Expand Up @@ -164,4 +161,4 @@ private void ClockSyncUx_Closed(object? sender, EventArgs e)
_clockSyncSnapshotOwner.ClockSyncSnapshotChanged -= ClockSyncSnapshotChanged;
_clockSyncSnapshotOwner = null;
}
}
}
3 changes: 2 additions & 1 deletion IoListTestingWindow.P0BenchUx.cs
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ private void ApplyP0BenchUx()
{
ConfigureP0StableFatColumns();
ConfigureP0AdaptiveHeaderActions();
ConfigureP2CompactHeader();
}

private void ConfigureP0StableFatColumns()
Expand Down Expand Up @@ -206,4 +207,4 @@ public object Convert(object value, Type targetType, object parameter, CultureIn

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> Binding.DoNothing;
}
}
67 changes: 67 additions & 0 deletions IoListTestingWindow.P2CompactHeader.cs
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Windows;
using System.Windows.Controls;

namespace ArIED61850Tester;

/// <summary>
/// P2 bench-facing header compaction.
///
/// P0 already separates operational actions from evidence/status actions. P2 only tightens
/// that existing hierarchy so the FAT header remains readable on normal engineering laptop
/// widths. Full evidence detail stays in the existing tooltips; protocol, evidence and grid
/// behavior are intentionally untouched.
/// </summary>
public partial class IoListTestingWindow
{
private void ConfigureP2CompactHeader()
{
if (_p0PrimaryHeaderActions == null || _p0SecondaryHeaderActions == null)
return;

_p0PrimaryHeaderActions.Margin = new Thickness(0);
_p0SecondaryHeaderActions.Margin = new Thickness(0, 3, 0, 0);

foreach (var button in _p0PrimaryHeaderActions.Children.OfType<Button>())
ApplyP2CompactButtonMetrics(button, secondary: false);

foreach (var button in _p0SecondaryHeaderActions.Children.OfType<Button>())
ApplyP2CompactButtonMetrics(button, secondary: true);

WorkspacePreviewToggle.Content = "Preview";
if (_cleanSessionButton != null)
_cleanSessionButton.Content = "Clean FAT";

ApplyP2CompactStatusMetrics(_clockSyncGlobalStatusText, 118, FontWeights.Medium);
ApplyP2CompactStatusMetrics(_clockSyncEvidenceText, 188, FontWeights.Normal);
}

private static void ApplyP2CompactButtonMetrics(Button button, bool secondary)
{
button.Padding = secondary
? new Thickness(8, 5, 8, 5)
: new Thickness(9, 6, 9, 6);
button.Margin = new Thickness(0, 0, 5, 0);
button.MinWidth = 0;
button.MinHeight = secondary ? 27 : 29;
if (secondary)
{
button.FontSize = 10.4;
button.FontWeight = FontWeights.Medium;
}
}

private static void ApplyP2CompactStatusMetrics(
TextBlock? text,
double maxWidth,
FontWeight fontWeight)
{
if (text == null)
return;

text.MaxWidth = maxWidth;
text.TextWrapping = TextWrapping.NoWrap;
text.TextTrimming = TextTrimming.CharacterEllipsis;
text.FontWeight = fontWeight;
text.FontSize = 10.2;
}
}
16 changes: 8 additions & 8 deletions IoListTestingWindow.SupplementalEvidence.cs
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ private void InstallSupplementalEvidenceControls()
if (_comtradeEvidenceButton != null || WorkspacePreviewToggle.Parent is not Panel actionPanel)
return;

_timeSyncEvidenceButton = CreateEvidenceButton("Time Sync · —", RefreshTimeSyncEvidence_Click);
_timeSyncEvidenceButton = CreateEvidenceButton("Sync · —", RefreshTimeSyncEvidence_Click);
_timeSyncEvidenceButton.ToolTip = "Capture IEC 61850 time-synchronization evidence for the selected IED";

_comtradeEvidenceButton = CreateEvidenceButton("COMTRADE · —", OpenComtradeEvidence_Click);
_comtradeEvidenceButton.ToolTip = "Browse relay fault records. A remote COMTRADE listing is sufficient File Service FAT evidence; download is optional.";

_cleanSessionButton = CreateEvidenceButton("New Clean FAT", NewCleanFatSession_Click);
_cleanSessionButton = CreateEvidenceButton("Clean FAT", NewCleanFatSession_Click);
_cleanSessionButton.ToolTip = "Archive all current FAT evidence and reset the project to a zero-evidence retest session";

var insertionIndex = actionPanel.Children.IndexOf(WorkspacePreviewToggle) + 1;
Expand All @@ -57,9 +57,9 @@ private Button CreateEvidenceButton(string content, RoutedEventHandler handler)
var button = new Button
{
Content = content,
Padding = new Thickness(10, 8, 10, 8),
Padding = new Thickness(8, 6, 8, 6),
Margin = new Thickness(0, 0, 6, 0),
MinWidth = 92
MinWidth = 0
};
if (TryFindResource("SoftButton") is Style style)
button.Style = style;
Expand All @@ -75,7 +75,7 @@ private void RefreshSupplementalEvidenceControls()
var ied = SelectedIed;
if (ied == null)
{
_timeSyncEvidenceButton.Content = "Time Sync · —";
_timeSyncEvidenceButton.Content = "Sync · —";
_timeSyncEvidenceButton.ToolTip = "Select an IED first";
_comtradeEvidenceButton.Content = "COMTRADE · —";
_comtradeEvidenceButton.ToolTip = "Select an IED first";
Expand All @@ -87,8 +87,8 @@ private void RefreshSupplementalEvidenceControls()
ied.IedName,
IoFatSupplementalEvidenceService.TimeSyncKind);
_timeSyncEvidenceButton.Content = timeSync == null
? "Time Sync · —"
: $"Time Sync · {timeSync.Verdict}";
? "Sync · —"
: $"Sync · {timeSync.Verdict}";
_timeSyncEvidenceButton.ToolTip = timeSync == null
? "No time-sync evidence captured yet. It will be captured automatically after FAT connection."
: $"{timeSync.DisplayText}\n{timeSync.Reason}\nCaptured {timeSync.RecordedAtUtc.ToLocalTime():yyyy-MM-dd HH:mm:ss}";
Expand Down Expand Up @@ -288,4 +288,4 @@ private static bool HasLiveValue(string? value)
!text.Equals("Unknown", StringComparison.OrdinalIgnoreCase) &&
!text.Contains("not probed", StringComparison.OrdinalIgnoreCase);
}
}
}
82 changes: 82 additions & 0 deletions tests/ARSAS.Tests/IoFatP2CompactHeaderRegressionTests.cs
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
namespace ARSAS.Tests;

public sealed class IoFatP2CompactHeaderRegressionTests
{
[Fact]
public void P2_ReusesP0PrimaryAndSecondaryHeaderHierarchy()
{
var p0 = File.ReadAllText(FindRepoFile("IoListTestingWindow.P0BenchUx.cs"));

Assert.Contains("ConfigureP0AdaptiveHeaderActions();", p0, StringComparison.Ordinal);
Assert.Contains("ConfigureP2CompactHeader();", p0, StringComparison.Ordinal);
Assert.Contains("_p0PrimaryHeaderActions", p0, StringComparison.Ordinal);
Assert.Contains("_p0SecondaryHeaderActions", p0, StringComparison.Ordinal);
Assert.Contains("ReferenceEquals(element, WorkspacePreviewToggle)", p0, StringComparison.Ordinal);
Assert.Contains("ReferenceEquals(element, _timeSyncEvidenceButton)", p0, StringComparison.Ordinal);
Assert.Contains("ReferenceEquals(element, _comtradeEvidenceButton)", p0, StringComparison.Ordinal);
Assert.Contains("ReferenceEquals(element, _cleanSessionButton)", p0, StringComparison.Ordinal);
Assert.Contains("ReferenceEquals(element, _clockSyncGlobalStatusText)", p0, StringComparison.Ordinal);
Assert.Contains("ReferenceEquals(element, _clockSyncEvidenceText)", p0, StringComparison.Ordinal);
}

[Fact]
public void P2_CompactsSecondaryControlsWithoutHidingEvidenceDetail()
{
var header = File.ReadAllText(FindRepoFile("IoListTestingWindow.P2CompactHeader.cs"));
var clock = File.ReadAllText(FindRepoFile("IoListTestingWindow.ClockSyncUx.cs"));
var supplemental = File.ReadAllText(FindRepoFile("IoListTestingWindow.SupplementalEvidence.cs"));

Assert.Contains("WorkspacePreviewToggle.Content = \"Preview\";", header, StringComparison.Ordinal);
Assert.Contains("_cleanSessionButton.Content = \"Clean FAT\";", header, StringComparison.Ordinal);
Assert.Contains("TextTrimming.CharacterEllipsis", header, StringComparison.Ordinal);
Assert.Contains("button.MinWidth = 0;", header, StringComparison.Ordinal);

Assert.Contains("Text = \"SNTP · —\"", clock, StringComparison.Ordinal);
Assert.Contains("\"SNTP · ON\"", clock, StringComparison.Ordinal);
Assert.Contains("\"SNTP · OFF\"", clock, StringComparison.Ordinal);
Assert.Contains("Rep {snapshot.ReplyCount}", clock, StringComparison.Ordinal);
Assert.Contains("BuildClockSyncEvidenceToolTip", clock, StringComparison.Ordinal);
Assert.Contains("Binding: {binding}", clock, StringComparison.Ordinal);
Assert.Contains("Client request seen: {snapshot.ClientRequestCount}", clock, StringComparison.Ordinal);
Assert.Contains("Mode 4 reply sent: {snapshot.ReplyCount}", clock, StringComparison.Ordinal);

Assert.Contains("CreateEvidenceButton(\"Sync · —\"", supplemental, StringComparison.Ordinal);
Assert.Contains("CreateEvidenceButton(\"Clean FAT\"", supplemental, StringComparison.Ordinal);
Assert.Contains("$\"Sync · {timeSync.Verdict}\"", supplemental, StringComparison.Ordinal);
Assert.Contains("timeSync.DisplayText", supplemental, StringComparison.Ordinal);
Assert.Contains("timeSync.Reason", supplemental, StringComparison.Ordinal);
Assert.Contains("ied.LatestComtradeRemotePath", supplemental, StringComparison.Ordinal);
}

[Fact]
public void P2_IsPresentationOnlyAndDoesNotTouchFatProtocolEvidenceOrGridSchema()
{
var header = File.ReadAllText(FindRepoFile("IoListTestingWindow.P2CompactHeader.cs"));

Assert.DoesNotContain("SetVirtualizationMode", header, StringComparison.Ordinal);
Assert.DoesNotContain("SetIsVirtualizing", header, StringComparison.Ordinal);
Assert.DoesNotContain("ReadObject", header, StringComparison.Ordinal);
Assert.DoesNotContain("ReadAsync", header, StringComparison.Ordinal);
Assert.DoesNotContain("CaptureCurrentEvidence", header, StringComparison.Ordinal);
Assert.DoesNotContain("ScheduleSave", header, StringComparison.Ordinal);
Assert.DoesNotContain("ARIEC61850", header, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain("grid.Columns", header, StringComparison.Ordinal);
Assert.DoesNotContain("ON RELAY TIME", header, StringComparison.Ordinal);
Assert.DoesNotContain("OFF RELAY TIME", header, StringComparison.Ordinal);
}

private static string FindRepoFile(string relativePath)
{
DirectoryInfo? directory = new(AppContext.BaseDirectory);
while (directory != null)
{
var candidate = Path.Combine(directory.FullName, relativePath);
if (File.Exists(candidate))
return candidate;
directory = directory.Parent;
}

throw new FileNotFoundException(
$"Could not locate repository file '{relativePath}' from '{AppContext.BaseDirectory}'.");
}
}
Loading

AltStyle によって変換されたページ (->オリジナル) /