diff --git a/.gitignore b/.gitignore index 57cc20a..7d7aa73 100644 --- a/.gitignore +++ b/.gitignore @@ -52,5 +52,6 @@ desktop/frontend/ # .specs *.specs .docs +.pi AGENTS.md \ No newline at end of file diff --git a/internal/app/model_types.go b/internal/app/model_types.go index 755a55f..ef6bd2a 100644 --- a/internal/app/model_types.go +++ b/internal/app/model_types.go @@ -29,7 +29,7 @@ const ( type Settings struct { NotificationsEnabled bool - NotificationSound bool + NotificationSound bool Theme string } @@ -113,12 +113,13 @@ var DefaultKeys = KeyMap{ key.WithHelp("s", "settings"), ), Back: key.NewBinding( - key.WithKeys("esc", "b"), - key.WithHelp("esc/b", "back"), + key.WithKeys("esc"), + key.WithHelp("esc", "back"), ), Quit: key.NewBinding( - key.WithKeys("q", "ctrl+c"), - key.WithHelp("q/ctrl+c", "quit"), + key.WithKeys("ctrl+c", "esc"), + key.WithHelp("ctrl+c", "quit"), + key.WithHelp("esc", "quit"), ), Help: key.NewBinding( key.WithKeys("?"), diff --git a/internal/i18n/locales/en.yaml b/internal/i18n/locales/en.yaml index 74e81e6..a5f02dc 100644 --- a/internal/i18n/locales/en.yaml +++ b/internal/i18n/locales/en.yaml @@ -158,10 +158,10 @@ tip_dashboard: "💡 Tip: Web dashboard at" # Settings UI settings_title: "⚙ Settings" -settings_nav_hint: "↑/↓ navigate • space/enter toggle • esc back" +settings_nav_hint: "↑/↓ Navigate • space/enter Toggle • esc Back" tunnel_logs: "📋 Tunnel Logs" -logs_nav_hint: "esc/b: back • ↑/↓: scroll" +logs_nav_hint: "esc: Back • ↑/↓: Scroll" # UI elements play_indicator: "▶" @@ -243,8 +243,8 @@ validation_required_fields: "Name and Port are required" # TUI Elements app_name_tui: "🎲 Foundry Tunnel Manager" -web: "web" -config: "config" +web: "Web" +config: "Config" # CLI uninstall_not_found: "ftm is not installed or not in PATH" diff --git a/internal/i18n/locales/es.yaml b/internal/i18n/locales/es.yaml index ea0fe7a..ab919a1 100644 --- a/internal/i18n/locales/es.yaml +++ b/internal/i18n/locales/es.yaml @@ -139,7 +139,7 @@ arrow_hint: "← → para cambiar" numbers_hint: "solo números" submit_new: "Crear Túnel" submit_edit: "Guardar Cambios" -form_nav_hint: "TAB: navegar • ENTER: enviar • ESC: cancelar" +form_nav_hint: "TAB: Navegar • ENTER: Enviar • ESC: Cancelar" # Empty states no_tunnels: "No hay túneles configurados" @@ -158,10 +158,10 @@ tip_dashboard: "💡 Tip: Panel web en" # Settings UI settings_title: "⚙ Configuración" -settings_nav_hint: "↑/↓ navegar • espacio/enter cambiar • esc volver" +settings_nav_hint: "↑/↓ Navegar • espacio/enter Cambiar • esc Volver" tunnel_logs: "📋 Logs del Túnel" -logs_nav_hint: "esc/b: volver • ↑/↓: scroll" +logs_nav_hint: "esc: Volver • ↑/↓: Scroll" # UI elements play_indicator: "▶" @@ -243,8 +243,8 @@ validation_required_fields: "Nombre y Puerto son requeridos" # TUI Elements app_name_tui: "🎲 Foundry Tunnel Manager" -web: "web" -config: "configuración" +web: "Web" +config: "Configuración" # CLI uninstall_not_found: "ftm no está instalado o no está en PATH" diff --git a/web-svelte/src/lib/components/Dropdown.svelte b/web-svelte/src/lib/components/Dropdown.svelte index 910a5e5..2571b8d 100644 --- a/web-svelte/src/lib/components/Dropdown.svelte +++ b/web-svelte/src/lib/components/Dropdown.svelte @@ -21,7 +21,7 @@ left: "left-auto right-0", right: "right-auto left-0", "top-left": "bottom-full mb-1.5 left-auto right-0", - "top-right": "bottom-full mb-1.5 right-auto left-0" + "top-right": "bottom-full mb-1.5 right-auto left-0", }; let t = $derived($translate); @@ -38,81 +38,63 @@ }: DropdownProps = $props(); let isOpen = $state(false); - let isAnimating = $state(false); let menuEl: HTMLDivElement | undefined = $state(); - const isVisible = $derived(isOpen || isAnimating); const menuPosition = $derived.by(() => { const vert = align.startsWith("top") ? "" : "top-full mt-1.5"; return `${POSITION_MAP[align]} ${vert}`; }); function open() { - if (isOpen) return; + if (isOpen || !menuEl) return; isOpen = true; - isAnimating = true; - requestAnimationFrame(() => { - if (!menuEl) return; - animate( - menuEl, - { opacity: 1, scale: 1, y: 0 }, - { type: "spring" }, - ).finished.then(() => { - isAnimating = false; - }); - }); + animate(menuEl, { opacity: 1, scale: 1, y: 0 }, { type: "spring" }); } function close() { - if (!isOpen || !menuEl) { - isOpen = false; - isAnimating = false; - return; - } + if (!isOpen || !menuEl) return; isOpen = false; - isAnimating = true; - animate( - menuEl, - { opacity: 0, scale: 1, y: -4 }, - { type: "spring" }, - ).finished.then(() => { - isAnimating = false; - }); + animate(menuEl, { opacity: 0, scale: 1, y: -4 }, { type: "spring" }); } - function toggle() { + function toggle(e: MouseEvent) { + e?.stopPropagation(); isOpen ? close() : open(); } function handleOutsideClick(e: MouseEvent) { - if (!(e.target as HTMLElement).closest(".dropdown-container")) close(); + if (!isOpen) return; + const target = e.target as HTMLElement; + if (target.closest(".dropdown-trigger") || target.closest(".dropdown-menu")) + return; + close(); + } + + function handleKeydown(e: KeyboardEvent) { + if (e.key === "Escape") close(); } $effect(() => { if (!isOpen) return; - document.addEventListener("click", handleOutsideClick); + document.addEventListener("click", handleOutsideClick, true); document.addEventListener("keydown", handleKeydown); return () => { - document.removeEventListener("click", handleOutsideClick); + document.removeEventListener("click", handleOutsideClick, true); document.removeEventListener("keydown", handleKeydown); }; }); - - function handleKeydown(e: KeyboardEvent) { - if (e.key === "Escape") close(); - }