src/lib/ui/renderers.ts

Total Symbols
13
Lines of Code
190
Avg Complexity
2.6
Avg Coverage
100.0%

File Relationships

graph LR renderFieldRow["renderFieldRow"] renderTypeBadge["renderTypeBadge"] renderMethodBadge["renderMethodBadge"] renderConfidenceBadge["renderConfidenceBadge"] renderFieldRow -->|calls| renderTypeBadge renderFieldRow -->|calls| renderMethodBadge renderFieldRow -->|calls| renderConfidenceBadge click renderFieldRow "../symbols/b51f1878ac9d34b2.html" click renderTypeBadge "../symbols/9c98fcbf039c92b3.html" click renderMethodBadge "../symbols/86ef5700ac98762f.html" click renderConfidenceBadge "../symbols/1577a0562e419cb2.html"

Architecture violations

View all

  • [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("");
}