src/lib/ui/renderers.ts
File Relationships
Architecture violations
- [warning] max-cyclomatic-complexity: 'renderFieldRow' has cyclomatic complexity 13 (max 10)
Symbols by Kind
function
9
interface
4
All Symbols
| Name | Kind | Visibility | Status | Lines | Signature |
|---|---|---|---|---|---|
| renderTypeBadge | function | exported- | 15-18 | renderTypeBadge(type: string, prefix = ""): : string |
|
| renderMethodBadge | function | exported- | 21-24 | renderMethodBadge(method: string, prefix = ""): : string |
|
| renderConfidenceBadge | function | exported- | 27-41 | renderConfidenceBadge(
confidence: number | undefined,
prefix = "",
): : string |
|
| FieldsTableOptions | interface | exported- | 45-52 | interface FieldsTableOptions |
|
| renderFieldsTableHeader | function | exported- | 55-72 | renderFieldsTableHeader(
options: FieldsTableOptions = {},
): : string |
|
| renderFieldRow | function | exported- | 75-100 | renderFieldRow(
field: DetectedFieldSummary,
index: number,
options: FieldsTableOptions = {},
): : string |
|
| renderFormCard | function | exported- | 105-119 | renderFormCard(form: SavedForm, prefix = ""): : string |
|
| LogEntry | interface | exported- | 124-128 | interface LogEntry |
|
| renderLogEntry | function | exported- | 131-138 | renderLogEntry(entry: LogEntry, prefix = ""): : string |
|
| ActionCardConfig | interface | exported- | 143-150 | interface ActionCardConfig |
|
| renderActionCard | function | exported- | 153-170 | renderActionCard(card: ActionCardConfig, prefix = ""): : string |
|
| TabConfig | interface | exported- | 175-179 | interface TabConfig |
|
| renderTabBar | function | exported- | 182-189 | renderTabBar(tabs: TabConfig[], prefix = ""): : string |
Full Source
/**
* Shared HTML renderers used across popup and devtools panel.
*
* All functions return HTML strings. Each accepts an optional `prefix` parameter
* for CSS class scoping.
*/
import type { DetectedFieldSummary, SavedForm } from "@/types";
import { escapeHtml, escapeAttr } from "./html-utils";
import { TYPE_COLORS, METHOD_COLORS, getConfidenceColor } from "./constants";
// ── Badges ───────────────────────────────────────────────────────────────────
/** Renders a colored badge for a field type. */
export function renderTypeBadge(type: string, prefix = ""): string {
const color = TYPE_COLORS[type] ?? "#64748b";
return `<span class="${prefix}type-badge" style="background:${color}">${escapeHtml(type)}</span>`;
}
/** Renders a colored badge for a detection method. */
export function renderMethodBadge(method: string, prefix = ""): string {
const color = METHOD_COLORS[method] ?? "#334155";
return `<span class="${prefix}method-badge" style="background:${color};color:#fff">${escapeHtml(method)}</span>`;
}
/** Renders a confidence progress bar with percentage label. */
export function renderConfidenceBadge(
confidence: number | undefined,
prefix = "",
): string {
const conf = confidence ?? 0;
const percent = Math.round(conf * 100);
const color = getConfidenceColor(conf);
return `
<span class="${prefix}confidence-bar">
<span class="${prefix}confidence-fill" style="width:${percent}%;background:${color}"></span>
</span>
<span style="font-size:10px;color:${color}">${percent}%</span>
`;
}
// ── Fields Table ─────────────────────────────────────────────────────────────
export interface FieldsTableOptions {
/** CSS class prefix for scoping (e.g. "fa-") */
prefix?: string;
/** Set of ignored selectors */
ignoredSelectors?: Set<string>;
/** Show actions column */
showActions?: boolean;
}
/** Renders the `<thead>` row for the detected-fields table. */
export function renderFieldsTableHeader(
options: FieldsTableOptions = {},
): string {
const p = options.prefix ?? "";
return `
<thead>
<tr>
<th>#</th>
<th>Tipo</th>
<th>Método</th>
<th>Confiança</th>
<th>ID / Name</th>
<th>Label</th>
${options.showActions !== false ? "<th>Ações</th>" : ""}
</tr>
</thead>
`;
}
/** Renders a single `<tr>` for a detected field. */
export function renderFieldRow(
field: DetectedFieldSummary,
index: number,
options: FieldsTableOptions = {},
): string {
const p = options.prefix ?? "";
const isIgnored = options.ignoredSelectors?.has(field.selector) ?? false;
const displayType = field.contextualType || field.fieldType;
const method = field.detectionMethod || "-";
return `
<tr class="${isIgnored ? `${p}row-ignored` : ""}">
<td class="${p}cell-num">${index}</td>
<td>${renderTypeBadge(displayType, p)}</td>
<td>${renderMethodBadge(method, p)}</td>
<td>${renderConfidenceBadge(field.detectionConfidence, p)}</td>
<td class="${p}cell-mono">${escapeHtml(field.id || field.name || "-")}</td>
<td>${escapeHtml(field.label || "-")}</td>
${
options.showActions !== false
? `<td class="${p}cell-actions" data-selector="${escapeAttr(field.selector)}" data-label="${escapeAttr(field.label || field.name || field.id || field.selector)}"></td>`
: ""
}
</tr>
`;
}
// ── Forms Cards ──────────────────────────────────────────────────────────────
/** Renders a card summarizing a saved form. */
export function renderFormCard(form: SavedForm, prefix = ""): string {
const fieldCount = Object.keys(form.fields).length;
const date = new Date(form.updatedAt).toLocaleDateString("pt-BR");
return `
<div class="${prefix}form-card" data-form-id="${escapeAttr(form.id)}">
<div class="${prefix}form-info">
<span class="${prefix}form-name">${escapeHtml(form.name)}</span>
<span class="${prefix}form-meta">${fieldCount} campos · ${date}</span>
<span class="${prefix}form-url">${escapeHtml(form.urlPattern)}</span>
</div>
<div class="${prefix}form-actions"></div>
</div>
`;
}
// ── Log Entries ──────────────────────────────────────────────────────────────
/** A structured log entry for display. */
export interface LogEntry {
time: string;
text: string;
type: string;
}
/** Renders a single log entry row. */
export function renderLogEntry(entry: LogEntry, prefix = ""): string {
return `
<div class="${prefix}log-entry ${prefix}log-${entry.type}">
<span class="${prefix}log-time">${entry.time}</span>
<span class="${prefix}log-text">${escapeHtml(entry.text)}</span>
</div>
`;
}
// ── Action Cards ─────────────────────────────────────────────────────────────
/** Configuration for an action-card button. */
export interface ActionCardConfig {
id: string;
icon: string;
label: string;
desc: string;
variant: "primary" | "secondary" | "outline";
active?: boolean;
}
/** Renders an action-card button (primary / secondary / outline). */
export function renderActionCard(card: ActionCardConfig, prefix = ""): string {
const variantClass =
card.variant === "primary"
? `${prefix}card-primary`
: card.variant === "secondary"
? `${prefix}card-secondary`
: `${prefix}card-outline`;
const activeClass = card.active ? " active" : "";
return `
<button class="${prefix}action-card ${variantClass}${activeClass}" id="${card.id}">
<span class="${prefix}card-icon">${card.icon}</span>
<span class="${prefix}card-label">${card.label}</span>
<span class="${prefix}card-desc">${card.desc}</span>
</button>
`;
}
// ── Tab Bar ──────────────────────────────────────────────────────────────────
/** Configuration for a tab in the tab bar. */
export interface TabConfig {
id: string;
label: string;
active?: boolean;
}
/** Renders a horizontal tab bar from tab configs. */
export function renderTabBar(tabs: TabConfig[], prefix = ""): string {
return tabs
.map(
(tab) =>
`<button class="${prefix}tab ${tab.active ? "active" : ""}" data-tab="${tab.id}">${tab.label}</button>`,
)
.join("");
}