import { createSignal, onMount, Show } from "solid-js"; import { generatePassword } from "../../shared/generate"; import type { PasswordPolicy } from "../../shared/types"; import { sendMessage, triggerPipPulse, triggerRing } from "../App"; import { popupCopy } from "../copy"; interface GeneratorDrawerProps { open: boolean; policy?: PasswordPolicy; onUse: (password: string) => void; /** Compact two-row layout for use inside entry edit mode */ compact?: boolean; } export function GeneratorDrawer(props: GeneratorDrawerProps) { const [password, setPassword] = createSignal(""); const [length, setLength] = createSignal(20); const [upper, setUpper] = createSignal(true); const [lower, setLower] = createSignal(true); const [numbers, setNumbers] = createSignal(true); const [symbols, setSymbols] = createSignal(true); const [forbiddenChars, setForbiddenChars] = createSignal(""); const [copied, setCopied] = createSignal(false); function initFromPolicy(policy: PasswordPolicy) { let nextLength = length(); let nextSymbols = symbols(); if (policy.minLength) nextLength = Math.max(nextLength, policy.minLength); if (policy.maxLength) nextLength = Math.min(nextLength, policy.maxLength); if (policy.forbidsSymbols) nextSymbols = false; if (policy.requiresSymbols) nextSymbols = true; setLength(nextLength); setSymbols(nextSymbols); setForbiddenChars(policy.forbiddenChars ?? ""); } onMount(async () => { if (props.policy) { initFromPolicy(props.policy); } else { const res = await sendMessage({ type: "GET_SITE_PASSWORD_POLICY" }); const data = (res as { ok: boolean; data?: PasswordPolicy }).data; if (data && Object.keys(data).length > 0) { initFromPolicy(data); } } regenerate(); }); function regenerate(options?: { length?: number; upper?: boolean; lower?: boolean; numbers?: boolean; symbols?: boolean; }) { setPassword( generatePassword({ length: options?.length ?? length(), includeUppercase: options?.upper ?? upper(), includeLowercase: options?.lower ?? lower(), includeNumbers: options?.numbers ?? numbers(), includeSymbols: options?.symbols ?? symbols(), forbiddenChars: forbiddenChars(), }) ); } async function copyPassword(button?: HTMLButtonElement) { const ok = await popupCopy(password(), "password"); if (ok) { triggerRing(); triggerPipPulse(); setCopied(true); if (button) { button.classList.remove("is-retreat"); const drain = document.createElement("span"); drain.className = "peach-copy-drain"; const bar = document.createElement("span"); bar.className = "peach-copy-drain-bar"; drain.appendChild(bar); button.appendChild(drain); window.setTimeout(() => { setCopied(false); button.classList.remove("is-copied"); button.classList.add("is-retreat"); drain.remove(); window.setTimeout(() => button.classList.remove("is-retreat"), 160); }, 1400); return; } window.setTimeout(() => setCopied(false), 1400); } } function toggleUppercase() { const next = !upper(); setUpper(next); regenerate({ upper: next }); } function toggleLowercase() { const next = !lower(); setLower(next); regenerate({ lower: next }); } function toggleNumbers() { const next = !numbers(); setNumbers(next); regenerate({ numbers: next }); } function toggleSymbols() { const next = !symbols(); setSymbols(next); regenerate({ symbols: next }); } const passwordFontSize = () => { const value = password(); if (value.length > 48) return "16px"; if (value.length > 24) return "20px"; return "24px"; }; const RefreshIcon = () => ( ); const CopyIcon = () => ( ); return (
{/* ── Compact layout ──────────────────────────────── */}
{password()}
{ const n = parseInt((e.target as HTMLInputElement).value, 10); setLength(n); regenerate({ length: n }); }} /> {length()}
{/* ── Full layout ─────────────────────────────────── */}

{password()}

length {length()}
{ const nextLength = parseInt( (event.target as HTMLInputElement).value, 10 ); setLength(nextLength); regenerate({ length: nextLength }); }} />
); }