import React, { useCallback, useEffect, useRef, useState } from "react"; import { useI18n } from "../../application/i18n/I18nProvider"; import { Button } from "../ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../ui/dialog"; export type UnsavedChoice = "save" | "discard" | "cancel"; interface Pending { fileName: string; resolve: (choice: UnsavedChoice) => void; } interface UnsavedChangesAPI { prompt: (fileName: string) => Promise; } export const UnsavedChangesProvider: React.FC<{ children: (api: UnsavedChangesAPI) => React.ReactNode; }> = ({ children }) => { const { t } = useI18n(); const [pending, setPending] = useState(null); const pendingRef = useRef(null); pendingRef.current = pending; const prompt = useCallback( (fileName: string) => new Promise((resolve) => { // Re-entrance: if a prior prompt is still pending, cancel it so its caller // doesn't hang forever waiting for a resolve that now belongs to a new prompt. const prior = pendingRef.current; if (prior) prior.resolve("cancel"); setPending({ fileName, resolve }); }), [], ); // Keep the singleton current during render so AppView close handlers and // hotkeys never race the post-commit useEffect registration window. promptSingleton = prompt; useEffect(() => () => { promptSingleton = null; }, []); // On unmount, resolve any in-flight prompt as "cancel" so awaiting callers don't leak. useEffect(() => () => { const prior = pendingRef.current; if (prior) { prior.resolve("cancel"); pendingRef.current = null; } }, []); const resolveWith = useCallback((choice: UnsavedChoice) => { if (!pending) return; pending.resolve(choice); setPending(null); }, [pending]); return ( <> {children({ prompt })} { if (!o) resolveWith("cancel"); }}> {t("sftp.editor.unsavedTitle")} {t("sftp.editor.unsavedMessage", { fileName: pending?.fileName ?? "" })} ); }; // --------------------------------------------------------------------------- // Module-level singleton — lets non-React code call the dialog without // prop-drilling. Registered/unregistered by UnsavedChangesProvider above. // --------------------------------------------------------------------------- let promptSingleton: ((fileName: string) => Promise) | null = null; export const promptUnsavedChanges = (fileName: string): Promise => { if (!promptSingleton) return Promise.resolve("cancel"); return promptSingleton(fileName); };