feat(transport-ui): import Map step + per-line diff view (M8 E2)
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
@using System.Text.Json
|
||||
|
||||
@*
|
||||
LineDiffView (Component #24, Task M8 E2).
|
||||
|
||||
Renders a single Code field's "lineDiff" payload — the line-level diff that the
|
||||
importer attaches to a Modified ImportPreviewItem's FieldDiffJson for code fields —
|
||||
as a compact, GitHub-style +/- list. Pure presentation: it takes an already-parsed
|
||||
JsonElement (the value of the "lineDiff" key) and walks its "hunks" array.
|
||||
|
||||
hunk op ∈ "context" | "add" | "remove" (lowercase):
|
||||
context → muted line, both old/new line numbers
|
||||
add → green-ish line, new line number only (no oldLineNo)
|
||||
remove → red-ish line, old line number only (no newLineNo)
|
||||
|
||||
When the payload's "truncated" flag is true a trailing marker summarises the
|
||||
addedCount / removedCount the diff could not show in full.
|
||||
|
||||
No third-party diff/charting library — Bootstrap utility classes + a small
|
||||
monospace block only.
|
||||
*@
|
||||
|
||||
@if (_hunks.Count == 0 && !_truncated)
|
||||
{
|
||||
<div class="text-muted small fst-italic" data-testid="line-diff-empty">No line-level changes.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="border rounded bg-body-tertiary font-monospace small overflow-auto"
|
||||
style="max-height: 22rem;" data-testid="line-diff">
|
||||
@foreach (var hunk in _hunks)
|
||||
{
|
||||
var (rowCls, gutter, sign) = hunk.Op switch
|
||||
{
|
||||
"add" => ("bg-success-subtle text-success-emphasis", FormatGutter(null, hunk.NewLineNo), "+"),
|
||||
"remove" => ("bg-danger-subtle text-danger-emphasis", FormatGutter(hunk.OldLineNo, null), "-"),
|
||||
_ => ("text-body-secondary", FormatGutter(hunk.OldLineNo, hunk.NewLineNo), " "),
|
||||
};
|
||||
<div class="d-flex @rowCls" data-testid="@($"line-diff-{hunk.Op}")">
|
||||
<span class="px-2 text-body-tertiary text-nowrap user-select-none"
|
||||
style="min-width: 6.5rem;">@gutter</span>
|
||||
<span class="px-1 text-nowrap user-select-none">@sign</span>
|
||||
<span class="px-2 text-break flex-grow-1" style="white-space: pre-wrap;">@hunk.Text</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (_truncated)
|
||||
{
|
||||
<div class="text-muted small fst-italic mt-1" data-testid="line-diff-truncated">
|
||||
… diff truncated (+@_addedCount / -@_removedCount more)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// The parsed value of a Modified item's <c>FieldDiffJson</c> code field's
|
||||
/// <c>lineDiff</c> key. When null the component renders nothing meaningful —
|
||||
/// callers should only render it for code fields that actually carry a
|
||||
/// <c>lineDiff</c> object.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public JsonElement? LineDiff { get; set; }
|
||||
|
||||
private readonly List<Hunk> _hunks = new();
|
||||
private bool _truncated;
|
||||
private int _addedCount;
|
||||
private int _removedCount;
|
||||
|
||||
private sealed record Hunk(string Op, string Text, int? OldLineNo, int? NewLineNo);
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
_hunks.Clear();
|
||||
_truncated = false;
|
||||
_addedCount = 0;
|
||||
_removedCount = 0;
|
||||
|
||||
if (LineDiff is not { ValueKind: JsonValueKind.Object } payload)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.TryGetProperty("truncated", out var truncatedEl)
|
||||
&& truncatedEl.ValueKind is JsonValueKind.True or JsonValueKind.False)
|
||||
{
|
||||
_truncated = truncatedEl.GetBoolean();
|
||||
}
|
||||
if (payload.TryGetProperty("addedCount", out var addedEl)
|
||||
&& addedEl.ValueKind == JsonValueKind.Number)
|
||||
{
|
||||
_addedCount = addedEl.GetInt32();
|
||||
}
|
||||
if (payload.TryGetProperty("removedCount", out var removedEl)
|
||||
&& removedEl.ValueKind == JsonValueKind.Number)
|
||||
{
|
||||
_removedCount = removedEl.GetInt32();
|
||||
}
|
||||
|
||||
if (payload.TryGetProperty("hunks", out var hunksEl)
|
||||
&& hunksEl.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var h in hunksEl.EnumerateArray())
|
||||
{
|
||||
if (h.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var op = h.TryGetProperty("op", out var opEl) && opEl.ValueKind == JsonValueKind.String
|
||||
? opEl.GetString() ?? "context"
|
||||
: "context";
|
||||
var text = h.TryGetProperty("text", out var textEl) && textEl.ValueKind == JsonValueKind.String
|
||||
? textEl.GetString() ?? string.Empty
|
||||
: string.Empty;
|
||||
int? oldLineNo = h.TryGetProperty("oldLineNo", out var oldEl) && oldEl.ValueKind == JsonValueKind.Number
|
||||
? oldEl.GetInt32()
|
||||
: null;
|
||||
int? newLineNo = h.TryGetProperty("newLineNo", out var newEl) && newEl.ValueKind == JsonValueKind.Number
|
||||
? newEl.GetInt32()
|
||||
: null;
|
||||
_hunks.Add(new Hunk(op, text, oldLineNo, newLineNo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatGutter(int? oldLineNo, int? newLineNo)
|
||||
{
|
||||
var left = oldLineNo?.ToString() ?? string.Empty;
|
||||
var right = newLineNo?.ToString() ?? string.Empty;
|
||||
return $"{left,3} {right,3}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user