134 lines
5.1 KiB
Plaintext
134 lines
5.1 KiB
Plaintext
@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}";
|
|
}
|
|
}
|