src/options/rules-section.ts

Total Symbols
8
Lines of Code
327
Avg Complexity
7.9
Symbol Types
1

File Relationships

graph LR loadRules["loadRules"] editRule["editRule"] updateRuleParamsSection["updateRuleParamsSection"] cancelEditRule["cancelEditRule"] renderOptionParamField["renderOptionParamField"] bindRulesEvents["bindRulesEvents"] collectRuleParams["collectRuleParams"] initRulesTab["initRulesTab"] loadRules -->|calls| editRule loadRules -->|calls| loadRules editRule -->|calls| updateRuleParamsSection cancelEditRule -->|calls| updateRuleParamsSection updateRuleParamsSection -->|calls| renderOptionParamField bindRulesEvents -->|calls| collectRuleParams bindRulesEvents -->|calls| loadRules bindRulesEvents -->|calls| cancelEditRule bindRulesEvents -->|calls| updateRuleParamsSection initRulesTab -->|calls| bindRulesEvents initRulesTab -->|calls| loadRules click loadRules "../symbols/9993895f2b3163a1.html" click editRule "../symbols/238046fb2c80772c.html" click updateRuleParamsSection "../symbols/40c364fb8f9a0ec0.html" click cancelEditRule "../symbols/fd39c6ea074f2721.html" click renderOptionParamField "../symbols/ddea4212bc84e23a.html" click bindRulesEvents "../symbols/3107cd11f10b1501.html" click collectRuleParams "../symbols/546661d5d9be8ecf.html" click initRulesTab "../symbols/d8b3be773c58391d.html"

Architecture violations

View all

  • [warning] max-cyclomatic-complexity: 'renderOptionParamField' has cyclomatic complexity 13 (max 10)
  • [warning] max-cyclomatic-complexity: 'bindRulesEvents' has cyclomatic complexity 13 (max 10)

Symbols by Kind

function 8

All Symbols

Name Kind Visibility Status Lines Signature
loadRules function - 22-69 loadRules(): : Promise<void>
editRule function - 71-107 editRule(rule: FieldRule): : void
cancelEditRule function - 109-132 cancelEditRule(): : void
updateRuleParamsSection function - 134-165 updateRuleParamsSection(existingParams?: GeneratorParams): : void
renderOptionParamField function - 167-208 renderOptionParamField( def: GeneratorParamDef, existingParams?: GeneratorParams, ): : string
collectRuleParams function - 210-248 collectRuleParams(): : GeneratorParams | undefined
bindRulesEvents function - 250-321 bindRulesEvents(): : void
initRulesTab function exported- 323-326 initRulesTab(): : void

Full Source

/**
 * Rules tab — list, create and delete field rules.
 */

import type { FieldRule, FieldType, GeneratorParams } from "@/types";
import {
  escapeHtml,
  generateId,
  showToast,
  ruleTypeSelect,
  ruleGeneratorSelect,
} from "./shared";
import { t } from "@/lib/i18n";
import {
  getGeneratorKey,
  getGeneratorParamDefs,
  type GeneratorParamDef,
} from "@/types/field-type-definitions";

let currentEditingRuleId: string | null = null;

async function loadRules(): Promise<void> {
  const rules = (await chrome.runtime.sendMessage({
    type: "GET_RULES",
  })) as FieldRule[];
  const list = document.getElementById("rules-list");
  if (!list) return;

  list.innerHTML = "";

  if (!Array.isArray(rules) || rules.length === 0) {
    list.innerHTML = `<div class="empty">${t("noRules")}</div>`;
    return;
  }

  for (const rule of rules) {
    const item = document.createElement("div");
    item.className = "rule-item";
    item.innerHTML = `
      <div class="rule-info">
        <strong>${escapeHtml(rule.urlPattern)}</strong>
        <span class="rule-selector">${escapeHtml(rule.fieldSelector)}</span>
        <span class="badge">${escapeHtml(rule.fieldType)}</span>
        ${rule.fixedValue ? `<span class="badge badge-fixed">${t("fixedLabel", [escapeHtml(rule.fixedValue)])}</span>` : ""}
        ${rule.generatorParams ? `<span class="badge badge-params">⚙ ${t("paramSectionTitle")}</span>` : ""}
        <span class="rule-priority">${t("rulePriority")} ${rule.priority}</span>
      </div>
      <div class="rule-actions">
        <button class="btn btn-sm btn-edit" data-rule-id="${escapeHtml(rule.id)}">Editar</button>
        <button class="btn btn-sm btn-delete" data-rule-id="${escapeHtml(rule.id)}">Excluir</button>
      </div>
    `;

    item.querySelector(".btn-edit")?.addEventListener("click", () => {
      editRule(rule);
    });

    item.querySelector(".btn-delete")?.addEventListener("click", async () => {
      await chrome.runtime.sendMessage({
        type: "DELETE_RULE",
        payload: rule.id,
      });
      await loadRules();
      showToast(t("toastRuleDeleted"));
    });

    list.appendChild(item);
  }
}

function editRule(rule: FieldRule): void {
  currentEditingRuleId = rule.id;

  (document.getElementById("rule-url") as HTMLInputElement).value =
    rule.urlPattern;
  (document.getElementById("rule-selector") as HTMLInputElement).value =
    rule.fieldSelector;
  (document.getElementById("rule-field-name") as HTMLInputElement).value =
    rule.fieldName || "";
  (document.getElementById("rule-fixed") as HTMLInputElement).value =
    rule.fixedValue || "";
  (document.getElementById("rule-priority") as HTMLInputElement).value = String(
    rule.priority,
  );

  // Setar os valores nos SearchableSelects
  ruleTypeSelect?.setValue(rule.fieldType);
  ruleGeneratorSelect?.setValue(rule.generator);
  setTimeout(() => {
    updateRuleParamsSection(rule.generatorParams);
  }, 0);

  // Scroll to form
  const form = document.querySelector(".card:last-of-type") as HTMLElement;
  if (form) form.scrollIntoView({ behavior: "smooth" });

  // Update button state
  const btn = document.getElementById("btn-save-rule");
  const cancelBtn = document.getElementById("btn-cancel-rule");
  if (btn) {
    btn.textContent = t("btnUpdateRule");
    btn.dataset.editing = "true";
  }
  if (cancelBtn) {
    cancelBtn.style.display = "block";
  }
}

function cancelEditRule(): void {
  currentEditingRuleId = null;

  // Clear form
  (document.getElementById("rule-url") as HTMLInputElement).value = "";
  (document.getElementById("rule-selector") as HTMLInputElement).value = "";
  (document.getElementById("rule-field-name") as HTMLInputElement).value = "";
  (document.getElementById("rule-fixed") as HTMLInputElement).value = "";
  (document.getElementById("rule-priority") as HTMLInputElement).value = "10";
  ruleTypeSelect?.setValue("");
  ruleGeneratorSelect?.setValue("");
  updateRuleParamsSection();

  // Reset button state
  const btn = document.getElementById("btn-save-rule");
  const cancelBtn = document.getElementById("btn-cancel-rule");
  if (btn) {
    btn.textContent = t("btnSaveRule");
    delete btn.dataset.editing;
  }
  if (cancelBtn) {
    cancelBtn.style.display = "none";
  }
}

function updateRuleParamsSection(existingParams?: GeneratorParams): void {
  const container = document.getElementById("rule-params-container");
  const fieldsDiv = document.getElementById("rule-params-fields");
  if (!container || !fieldsDiv) return;

  const generatorValue = ruleGeneratorSelect?.getValue() ?? "";

  if (
    !generatorValue ||
    generatorValue === "auto" ||
    generatorValue === "ai" ||
    generatorValue === "tensorflow"
  ) {
    container.style.display = "none";
    fieldsDiv.innerHTML = "";
    return;
  }

  const generatorKey = getGeneratorKey(generatorValue as FieldType);
  const paramDefs = generatorKey ? getGeneratorParamDefs(generatorKey) : [];

  if (paramDefs.length === 0) {
    container.style.display = "none";
    fieldsDiv.innerHTML = "";
    return;
  }

  fieldsDiv.innerHTML = paramDefs
    .map((def) => renderOptionParamField(def, existingParams))
    .join("");
  container.style.display = "block";
}

function renderOptionParamField(
  def: GeneratorParamDef,
  existingParams?: GeneratorParams,
): string {
  const label = t(def.labelKey) || def.labelKey;
  const currentValue = existingParams?.[def.key] ?? def.defaultValue;

  if (def.type === "select" && def.selectOptions) {
    const options = def.selectOptions
      .map((opt) => {
        const optLabel = t(opt.labelKey) || opt.labelKey;
        const selected = opt.value === currentValue ? "selected" : "";
        return `<option value="${escapeHtml(opt.value)}" ${selected}>${escapeHtml(optLabel)}</option>`;
      })
      .join("");
    return `
      <div class="form-group" style="min-width:150px;">
        <label>${escapeHtml(label)}</label>
        <select data-param-key="${def.key}">${options}</select>
      </div>`;
  }

  if (def.type === "boolean") {
    const checked = currentValue ? "checked" : "";
    return `
      <div class="form-group" style="min-width:150px;">
        <label style="display:flex;align-items:center;gap:6px;cursor:pointer;">
          <input type="checkbox" data-param-key="${def.key}" ${checked} />
          ${escapeHtml(label)}
        </label>
      </div>`;
  }

  const min = def.min != null ? `min="${def.min}"` : "";
  const max = def.max != null ? `max="${def.max}"` : "";
  const step = def.step != null ? `step="${def.step}"` : "";
  return `
    <div class="form-group" style="min-width:120px;">
      <label>${escapeHtml(label)}</label>
      <input type="number" data-param-key="${def.key}" value="${currentValue ?? ""}" ${min} ${max} ${step} />
    </div>`;
}

function collectRuleParams(): GeneratorParams | undefined {
  const fieldsDiv = document.getElementById("rule-params-fields");
  if (!fieldsDiv) return undefined;

  const inputs = fieldsDiv.querySelectorAll<HTMLInputElement>(
    "input[data-param-key]",
  );
  const selects = fieldsDiv.querySelectorAll<HTMLSelectElement>(
    "select[data-param-key]",
  );
  if (inputs.length === 0 && selects.length === 0) return undefined;

  const params: Record<string, unknown> = {};
  let hasAny = false;

  inputs.forEach((input) => {
    const key = input.dataset.paramKey!;
    if (input.type === "checkbox") {
      params[key] = input.checked;
      hasAny = true;
    } else if (input.type === "number") {
      const val = parseFloat(input.value);
      if (!isNaN(val)) {
        params[key] = val;
        hasAny = true;
      }
    }
  });

  selects.forEach((select) => {
    const key = select.dataset.paramKey!;
    if (select.value) {
      params[key] = select.value;
      hasAny = true;
    }
  });

  return hasAny ? (params as GeneratorParams) : undefined;
}

function bindRulesEvents(): void {
  document
    .getElementById("btn-save-rule")
    ?.addEventListener("click", async () => {
      const urlPattern = (
        document.getElementById("rule-url") as HTMLInputElement
      ).value.trim();
      const fieldSelector = (
        document.getElementById("rule-selector") as HTMLInputElement
      ).value.trim();

      if (!urlPattern || !fieldSelector) {
        showToast(t("errorFillUrlAndSelector"), "error");
        return;
      }

      const fieldTypeValue = ruleTypeSelect?.getValue().trim() ?? "";
      if (!fieldTypeValue) {
        showToast(t("errorSelectFieldType"), "error");
        return;
      }

      const isUpdating = !!currentEditingRuleId;
      const rule: FieldRule = {
        id: currentEditingRuleId || generateId(),
        urlPattern,
        fieldSelector,
        fieldName:
          (
            document.getElementById("rule-field-name") as HTMLInputElement
          ).value.trim() || undefined,
        fieldType: fieldTypeValue as FieldType,
        generator: (ruleGeneratorSelect?.getValue() ??
          "auto") as FieldRule["generator"],
        generatorParams: collectRuleParams(),
        fixedValue:
          (
            document.getElementById("rule-fixed") as HTMLInputElement
          ).value.trim() || undefined,
        priority:
          parseInt(
            (document.getElementById("rule-priority") as HTMLInputElement)
              .value,
            10,
          ) || 10,
        createdAt: isUpdating
          ? (
              (await chrome.runtime.sendMessage({
                type: "GET_RULES",
              })) as FieldRule[]
            ).find((r) => r.id === currentEditingRuleId)?.createdAt ||
            Date.now()
          : Date.now(),
        updatedAt: Date.now(),
      };

      await chrome.runtime.sendMessage({ type: "SAVE_RULE", payload: rule });
      await loadRules();

      cancelEditRule();
      showToast(isUpdating ? t("toastRuleUpdated") : t("toastRuleSaved"));
    });

  document.getElementById("btn-cancel-rule")?.addEventListener("click", () => {
    cancelEditRule();
    showToast(t("toastEditCancelled"));
  });

  ruleGeneratorSelect?.on("change", () => {
    updateRuleParamsSection();
  });
}

export function initRulesTab(): void {
  bindRulesEvents();
  void loadRules();
}