src/lib/ui/components/record-tab-view.tsx

Total Symbols
9
Lines of Code
346
Avg Complexity
1.0
Symbol Types
3

File Relationships

graph LR RecordTabCallbacks["RecordTabCallbacks"] onSaveEdit["onSaveEdit"] onExport["onExport"] RecordTabCallbacks -->|calls| onSaveEdit RecordTabCallbacks -->|calls| onExport click RecordTabCallbacks "../symbols/3231ef572a5c128f.html" click onSaveEdit "../symbols/4da18155cf8b7f44.html" click onExport "../symbols/d12261bbd260eb6b.html"

Symbols by Kind

method 4
interface 3
function 2

All Symbols

Name Kind Visibility Status Lines Signature
RecordTabCallbacks interface exported- 16-28 interface RecordTabCallbacks
RecordTabViewProps interface exported- 30-36 interface RecordTabViewProps
StepRowProps interface - 40-48 interface StepRowProps
setEditValue method - 79-79 setEditValue((e.target as HTMLInputElement).value)
onSaveEdit method - 90-90 onSaveEdit(index, editValue)
onEdit method - 112-112 onEdit(index)
handleSaveEdit function - 171-174 handleSaveEdit(index: number, value: string): : void
handleCancelEdit function - 176-178 handleCancelEdit(): : void
onExport method - 318-318 onExport("playwright")

Full Source

/**
 * RecordTabView — Preact component for the DevTools Record tab.
 *
 * Handles recording controls, steps table with inline value editing,
 * export section with optional AI optimization, and the ready-script state.
 */

import { h, Fragment } from "preact";
import { useState } from "preact/hooks";
import type { RecordStep, RecordingState } from "@/devtools/panel-state";
import { t } from "@/lib/i18n";
import { STEP_ICONS } from "@/devtools/panel-utils";

// ── Types ─────────────────────────────────────────────────────────────────────

export interface RecordTabCallbacks {
  onStart: () => void;
  onStop: () => void;
  onPause: () => void;
  onResume: () => void;
  onClear: () => void;
  onExport: (framework: string) => void;
  onCopyScript: () => void;
  onDismissScript: () => void;
  onToggleOptimizeAI: (checked: boolean) => void;
  onRemoveStep: (index: number) => void;
  onUpdateStep: (index: number, value: string) => void;
}

export interface RecordTabViewProps extends RecordTabCallbacks {
  recordingState: RecordingState;
  steps: RecordStep[];
  optimizeWithAI: boolean;
  isOptimizing: boolean;
  readyScript: { script: string; framework: string } | null;
}

// ── Step row with inline editing ──────────────────────────────────────────────

interface StepRowProps {
  step: RecordStep;
  index: number;
  editing: boolean;
  onEdit: (index: number) => void;
  onCancelEdit: () => void;
  onSaveEdit: (index: number, value: string) => void;
  onRemove: (index: number) => void;
}

function StepRow({
  step,
  index,
  editing,
  onEdit,
  onCancelEdit,
  onSaveEdit,
  onRemove,
}: StepRowProps) {
  const [editValue, setEditValue] = useState(step.value ?? "");
  const icon = STEP_ICONS[step.type] ?? "❓";
  const displayValue =
    step.value ?? step.label ?? (step.waitMs ? `${step.waitMs}ms` : "-");

  return (
    <tr>
      <td class="cell-num">{index + 1}</td>
      <td>
        {icon} {step.type}
      </td>
      <td class="cell-mono">{step.selector ?? step.url ?? "-"}</td>
      <td class="cell-value">
        {editing ? (
          <Fragment>
            <input
              type="text"
              class="edit-input step-edit-input"
              value={editValue}
              onInput={(e) =>
                setEditValue((e.target as HTMLInputElement).value)
              }
              onKeyDown={(e) => {
                if (e.key === "Enter") onSaveEdit(index, editValue);
                else if (e.key === "Escape") onCancelEdit();
              }}
              autoFocus
            />
            <button
              class="icon-btn step-edit-save"
              title="✓"
              onClick={() => onSaveEdit(index, editValue)}
            >
              ✅
            </button>
            <button
              class="icon-btn step-edit-cancel"
              title="✕"
              onClick={onCancelEdit}
            >
              ❌
            </button>
          </Fragment>
        ) : (
          <span class="step-value-text" title={t("actionEdit")}>
            {displayValue}
          </span>
        )}
      </td>
      <td class="cell-actions">
        <button
          class="icon-btn"
          title={t("actionEdit")}
          onClick={() => onEdit(index)}
        >
          ✏️
        </button>
        <button class="icon-btn" title="🗑️" onClick={() => onRemove(index)}>
          🗑️
        </button>
      </td>
    </tr>
  );
}

// ── RecordTabView ─────────────────────────────────────────────────────────────

export function RecordTabView({
  recordingState,
  steps,
  optimizeWithAI,
  isOptimizing,
  readyScript,
  onStart,
  onStop,
  onPause,
  onResume,
  onClear,
  onExport,
  onCopyScript,
  onDismissScript,
  onToggleOptimizeAI,
  onRemoveStep,
  onUpdateStep,
}: RecordTabViewProps) {
  const [editingStepIndex, setEditingStepIndex] = useState<number | null>(null);

  const isIdle = recordingState === "idle";
  const isRecording = recordingState === "recording";
  const isPaused = recordingState === "paused";
  const isStopped = recordingState === "stopped";
  const hasSteps = steps.length > 0;

  const statusText = isRecording
    ? t("recordStatusRecording")
    : isPaused
      ? t("recordStatusPaused")
      : isStopped
        ? (t("recordStatusStopped") ?? t("recordStatusIdle"))
        : t("recordStatusIdle");

  const indicatorClass = isRecording
    ? "recording"
    : isPaused
      ? "paused"
      : isStopped
        ? "stopped"
        : "";

  const showTable = !isIdle || hasSteps;
  const showExport = (isStopped || isPaused) && hasSteps;

  function handleSaveEdit(index: number, value: string): void {
    onUpdateStep(index, value);
    setEditingStepIndex(null);
  }

  function handleCancelEdit(): void {
    setEditingStepIndex(null);
  }

  return (
    <div class="record-section">
      {/* Controls */}
      <div class="record-controls">
        {isIdle ? (
          <button class="action-card primary" onClick={onStart}>
            <span class="card-icon">🔴</span>
            <span class="card-label">{t("recordStart")}</span>
            <span class="card-desc">{t("recordStartDesc")}</span>
          </button>
        ) : isRecording || isPaused ? (
          <Fragment>
            <button class="action-card btn-danger" onClick={onStop}>
              <span class="card-icon">⏹️</span>
              <span class="card-label">{t("recordStop")}</span>
            </button>
            <button
              class="action-card secondary"
              onClick={isPaused ? onResume : onPause}
            >
              <span class="card-icon">{isPaused ? "▶️" : "⏸️"}</span>
              <span class="card-label">
                {isPaused ? t("recordResume") : t("recordPause")}
              </span>
            </button>
          </Fragment>
        ) : (
          <Fragment>
            <button class="action-card primary" onClick={onStart}>
              <span class="card-icon">🔴</span>
              <span class="card-label">{t("recordStart")}</span>
              <span class="card-desc">{t("recordStartDesc")}</span>
            </button>
            {hasSteps && (
              <button class="action-card btn-danger" onClick={onClear}>
                <span class="card-icon">🗑️</span>
                <span class="card-label">{t("recordClear") ?? "Limpar"}</span>
              </button>
            )}
          </Fragment>
        )}
      </div>

      {/* Status */}
      <div class="record-status">
        <span class={`record-indicator ${indicatorClass}`} />
        <span>{statusText}</span>
        <span class="fields-count">
          {steps.length} {t("recordStepCount")}
        </span>
      </div>

      {/* Steps table */}
      <div class="table-wrap">
        {showTable ? (
          <table class="fields-table">
            <thead>
              <tr>
                <th>#</th>
                <th>{t("recordColAction")}</th>
                <th>{t("recordColSelector")}</th>
                <th>{t("recordColValue")}</th>
                <th>{t("columnActions")}</th>
              </tr>
            </thead>
            <tbody>
              {steps.length === 0 ? (
                <tr>
                  <td colspan={5} class="empty">
                    {t("recordNoSteps")}
                  </td>
                </tr>
              ) : (
                steps.map((step, i) => (
                  <StepRow
                    key={i}
                    step={step}
                    index={i}
                    editing={editingStepIndex === i}
                    onEdit={setEditingStepIndex}
                    onCancelEdit={handleCancelEdit}
                    onSaveEdit={handleSaveEdit}
                    onRemove={onRemoveStep}
                  />
                ))
              )}
            </tbody>
          </table>
        ) : (
          <div class="empty">{t("recordClickStart")}</div>
        )}
      </div>

      {/* Export section */}
      {showExport && (
        <div class="record-export">
          {readyScript ? (
            <Fragment>
              <div class="record-export-title">
                {t("recordScriptReady")} ({readyScript.framework})
              </div>
              <div class="record-export-buttons">
                <button class="btn btn-copy-script" onClick={onCopyScript}>
                  📋 {t("recordCopyScript")}
                </button>
                <button
                  class="btn btn-dismiss-script"
                  onClick={onDismissScript}
                >
                  ✕
                </button>
              </div>
            </Fragment>
          ) : (
            <Fragment>
              <div class="record-export-title">{t("recordExportTitle")}</div>
              <label class="record-ai-toggle" title={t("recordOptimizeAIDesc")}>
                <input
                  type="checkbox"
                  checked={optimizeWithAI}
                  disabled={isOptimizing}
                  onChange={(e) =>
                    onToggleOptimizeAI((e.target as HTMLInputElement).checked)
                  }
                />
                <span>✨ {t("recordOptimizeAI")}</span>
              </label>
              <div class="record-export-buttons">
                {isOptimizing ? (
                  <div class="export-loading-overlay">
                    <div class="export-loading-spinner" />
                    <span>{t("logRecordOptimizing")}</span>
                  </div>
                ) : (
                  <Fragment>
                    <button
                      class="btn"
                      disabled={isOptimizing}
                      onClick={() => onExport("playwright")}
                    >
                      🎭 Playwright
                    </button>
                    <button
                      class="btn"
                      disabled={isOptimizing}
                      onClick={() => onExport("cypress")}
                    >
                      🌲 Cypress
                    </button>
                    <button
                      class="btn"
                      disabled={isOptimizing}
                      onClick={() => onExport("pest")}
                    >
                      🐘 Pest/Dusk
                    </button>
                  </Fragment>
                )}
              </div>
            </Fragment>
          )}
        </div>
      )}
    </div>
  );
}