import { useKeyboard, useTerminalDimensions } from '@opentui/react'; import { useEffect, useRef, useState } from 'react'; import { sanitizeTerminalText } from '../../utils/terminal'; import { computeOverlayRect } from '../layout'; import { dispatchError, useAppDispatch, useAppState } from '../state/context'; import { terminalCellWidth, truncateTerminalText } from '../text'; import { colors } from '../theme'; export function ConfirmDialog() { const state = useAppState(); const dispatch = useAppDispatch(); const { width: terminalWidth, height: terminalHeight } = useTerminalDimensions(); const action = state.confirmAction; const [pending, setPending] = useState(false); const [actionError, setActionError] = useState(null); const pendingRef = useRef(false); const mountedRef = useRef(true); const operationRef = useRef(0); const layout = computeOverlayRect(terminalWidth, terminalHeight, { widthRatio: 0.5, minWidth: 24, height: 7, leftRatio: 0.25, topRatio: 0.4, }); useEffect(() => { mountedRef.current = true; return () => { mountedRef.current = false; operationRef.current += 1; }; }, []); useKeyboard((key) => { if (pendingRef.current) return; if (key.name === 'y' || key.name === 'enter' || key.name === 'return') { if (action) { pendingRef.current = true; setPending(true); setActionError(null); const operation = operationRef.current + 1; operationRef.current = operation; void Promise.resolve() .then(() => action.onConfirm()) .then(() => { if (!mountedRef.current || operationRef.current !== operation) return; dispatch({ type: 'SET_CONFIRM', action: null }); }) .catch((err) => { if (!mountedRef.current || operationRef.current !== operation) return; pendingRef.current = false; setPending(false); const message = sanitizeTerminalText(err instanceof Error ? err.message : String(err)); setActionError(message); dispatchError(dispatch, err); }); } return; } if (key.name === 'n' || key.name === 'escape') { dispatch({ type: 'SET_CONFIRM', action: null }); return; } }); if (!action) return null; const message = sanitizeTerminalText(action.message); const options = 'y yes n no'; return ( {layout.contentHeight >= 1 ? ( {truncateTerminalText(message, layout.contentWidth)} ) : null} {layout.contentHeight >= 3 && !actionError ? : null} {pending && layout.contentHeight >= 2 ? ( {truncateTerminalText('Working... input is locked', layout.contentWidth)} ) : layout.contentHeight >= 2 && terminalCellWidth(options) <= layout.contentWidth ? ( y {' yes '} n {' no'} ) : layout.contentHeight >= 2 ? ( {truncateTerminalText(options, layout.contentWidth)} ) : null} {actionError && layout.contentHeight >= 3 ? ( {truncateTerminalText(sanitizeTerminalText(actionError), layout.contentWidth)} ) : null} ); }