170 lines
5.4 KiB
TypeScript
170 lines
5.4 KiB
TypeScript
import { useState } from 'react';
|
||
|
||
import type { ParserDiagnostic } from '../onenote/model/diagnostics.js';
|
||
import {
|
||
DEFAULT_UI_RENDER_LIMITS,
|
||
limitUiString,
|
||
resolveUiLimit,
|
||
} from './ui-render-budget.js';
|
||
|
||
interface DiagnosticsPanelProps {
|
||
diagnostics: ParserDiagnostic[];
|
||
title?: string;
|
||
pageSize?: number;
|
||
}
|
||
|
||
function formatOffset(offset: number): string {
|
||
return `0x${offset.toString(16).toUpperCase()}`;
|
||
}
|
||
|
||
export function DiagnosticsPanel({
|
||
diagnostics,
|
||
title = 'Diagnostics',
|
||
pageSize: requestedPageSize,
|
||
}: DiagnosticsPanelProps) {
|
||
const [requestedPage, setRequestedPage] = useState(0);
|
||
const pageSize = resolveUiLimit(
|
||
requestedPageSize,
|
||
DEFAULT_UI_RENDER_LIMITS.maxDiagnosticsPerPage
|
||
);
|
||
const pageCount = Math.max(1, Math.ceil(diagnostics.length / pageSize));
|
||
const page = Math.min(requestedPage, pageCount - 1);
|
||
const firstDiagnostic = page * pageSize;
|
||
const limitedTitle = limitUiString(
|
||
title,
|
||
DEFAULT_UI_RENDER_LIMITS.maxPanelTitleCharacters
|
||
);
|
||
const displayedDiagnostics = diagnostics
|
||
.slice(firstDiagnostic, firstDiagnostic + pageSize)
|
||
.map((diagnostic, index) => {
|
||
const code = limitUiString(
|
||
diagnostic.code,
|
||
DEFAULT_UI_RENDER_LIMITS.maxDiagnosticCodeCharacters
|
||
);
|
||
const message = limitUiString(
|
||
diagnostic.message,
|
||
DEFAULT_UI_RENDER_LIMITS.maxDiagnosticMessageCharacters
|
||
);
|
||
const structure =
|
||
diagnostic.structure === undefined
|
||
? undefined
|
||
: limitUiString(
|
||
diagnostic.structure,
|
||
DEFAULT_UI_RENDER_LIMITS.maxDiagnosticContextCharacters
|
||
);
|
||
const propertyId =
|
||
diagnostic.propertyId === undefined
|
||
? undefined
|
||
: limitUiString(
|
||
diagnostic.propertyId,
|
||
DEFAULT_UI_RENDER_LIMITS.maxDiagnosticContextCharacters
|
||
);
|
||
return {
|
||
diagnostic,
|
||
index: firstDiagnostic + index,
|
||
code: code.value,
|
||
message: message.value,
|
||
structure: structure?.value,
|
||
propertyId: propertyId?.value,
|
||
truncated:
|
||
code.truncated ||
|
||
message.truncated ||
|
||
structure?.truncated === true ||
|
||
propertyId?.truncated === true,
|
||
};
|
||
});
|
||
const textTruncated =
|
||
limitedTitle.truncated ||
|
||
displayedDiagnostics.some((diagnostic) => diagnostic.truncated);
|
||
|
||
return (
|
||
<section className="diagnostics" aria-labelledby="diagnostics-title">
|
||
<div className="panel-heading">
|
||
<div>
|
||
<p className="eyebrow">Parser details</p>
|
||
<h2 id="diagnostics-title">{limitedTitle.value}</h2>
|
||
</div>
|
||
<span className="count-badge">{diagnostics.length}</span>
|
||
</div>
|
||
|
||
{diagnostics.length === 0 ? (
|
||
<p className="empty-message">No parser diagnostics for this view.</p>
|
||
) : (
|
||
<ul className="diagnostic-list">
|
||
{displayedDiagnostics.map((displayed) => (
|
||
<li
|
||
className={`diagnostic diagnostic--${displayed.diagnostic.severity}`}
|
||
key={displayed.index}
|
||
>
|
||
<div className="diagnostic__summary">
|
||
<strong>{displayed.code}</strong>
|
||
<span>{displayed.diagnostic.severity}</span>
|
||
</div>
|
||
<p>{displayed.message}</p>
|
||
{displayed.structure !== undefined ||
|
||
displayed.diagnostic.offset !== undefined ||
|
||
displayed.propertyId !== undefined ? (
|
||
<dl className="diagnostic__context">
|
||
{displayed.structure !== undefined ? (
|
||
<>
|
||
<dt>Structure</dt>
|
||
<dd>{displayed.structure}</dd>
|
||
</>
|
||
) : null}
|
||
{displayed.diagnostic.offset !== undefined ? (
|
||
<>
|
||
<dt>Offset</dt>
|
||
<dd>{formatOffset(displayed.diagnostic.offset)}</dd>
|
||
</>
|
||
) : null}
|
||
{displayed.propertyId !== undefined ? (
|
||
<>
|
||
<dt>Property</dt>
|
||
<dd>{displayed.propertyId}</dd>
|
||
</>
|
||
) : null}
|
||
</dl>
|
||
) : null}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
{diagnostics.length > 0 ? (
|
||
<nav className="panel-heading" aria-label="Diagnostic pages">
|
||
<p>
|
||
Showing diagnostics {firstDiagnostic + 1}–
|
||
{firstDiagnostic + displayedDiagnostics.length} of{' '}
|
||
{diagnostics.length}.
|
||
</p>
|
||
<div>
|
||
<button
|
||
type="button"
|
||
disabled={page === 0}
|
||
onClick={() => setRequestedPage(Math.max(0, page - 1))}
|
||
>
|
||
Previous
|
||
</button>{' '}
|
||
<span>
|
||
Page {page + 1} of {pageCount}
|
||
</span>{' '}
|
||
<button
|
||
type="button"
|
||
disabled={page + 1 >= pageCount}
|
||
onClick={() =>
|
||
setRequestedPage(Math.min(pageCount - 1, page + 1))
|
||
}
|
||
>
|
||
Next
|
||
</button>
|
||
</div>
|
||
</nav>
|
||
) : null}
|
||
{textTruncated ? (
|
||
<p className="render-limit-notice" role="status">
|
||
Long diagnostic text was shortened for safe browser display.
|
||
</p>
|
||
) : null}
|
||
</section>
|
||
);
|
||
}
|