import { useEffect, useRef, useState } from "react"; import { Plus, Trash2, Eye, EyeOff, Table, FileText } from "lucide-react"; import { Button, Input } from "@/components/ui"; interface Row { /** Stable across edits, so deleting a row doesn't shift the reveal state * (or input focus) onto its neighbour. */ id: string; key: string; value: string; } const SENSITIVE_RE = /(PASS|SECRET|TOKEN|KEY|APIKEY|PWD|CREDENTIAL)/i; let nextRowId = 0; const newRow = (key = "", value = ""): Row => ({ id: `r${nextRowId++}`, key, value }); function parseEnv(text: string): Row[] { return text .split("\n") .filter((l) => l.trim() && !l.trim().startsWith("#") && l.includes("=")) .map((l) => { const idx = l.indexOf("="); return newRow(l.slice(0, idx).trim(), l.slice(idx + 1)); }); } function serialize(rows: Row[]): string { const named = rows.filter((r) => r.key.trim()); return named.map((r) => `${r.key.trim()}=${r.value}`).join("\n") + (named.length ? "\n" : ""); } const QUICK = [ { key: "PUID", value: "1000" }, { key: "PGID", value: "1000" }, { key: "TZ", value: "Europe/Berlin" }, ]; export function EnvEditor({ value, onChange, }: { value: string; onChange: (v: string) => void; }) { const [mode, setMode] = useState<"table" | "raw">("table"); const [reveal, setReveal] = useState>({}); // The rows are owned here rather than derived from `value` on every render: // serialize() drops rows with an empty key, so a freshly added (still blank) // row would be thrown away before it could ever be typed into — which is why // "Add variable" appeared to do nothing. const [rows, setRows] = useState(() => parseEnv(value)); const lastEmitted = useRef(value); useEffect(() => { // Only re-parse when `value` changed somewhere else (loading a stack, // editing in raw mode); echoes of our own edits must not clobber blank rows. if (value !== lastEmitted.current) { lastEmitted.current = value; setRows(parseEnv(value)); } }, [value]); const update = (next: Row[]) => { setRows(next); const text = serialize(next); lastEmitted.current = text; onChange(text); }; const setRow = (id: string, patch: Partial) => update(rows.map((r) => (r.id === id ? { ...r, ...patch } : r))); const addRow = (row?: { key: string; value: string }) => update([...rows, row ? newRow(row.key, row.value) : newRow()]); const delRow = (id: string) => update(rows.filter((r) => r.id !== id)); const tabClass = (active: boolean) => active ? "flex items-center gap-1 rounded bg-accent px-2 py-1 text-xs text-white dark:bg-accent-dark dark:text-slate-900" : "flex items-center gap-1 rounded border border-slate-300 px-2 py-1 text-xs dark:border-slate-600"; return (