fix(play): ask before Escape ends session

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-09-30 08:53:48 +08:00
parent 143d141aa6
commit d228b9aab7
11 changed files with 131 additions and 2 deletions
+62
View File
@@ -203,6 +203,7 @@ export function ControlApp() {
y: number;
placementId: string;
} | null>(null);
const [stopConfirmOpen, setStopConfirmOpen] = useState(false);
const [sceneView, sceneViewApi] = useSceneViewState();
const [sceneViewDraft, setSceneViewDraft] = useState<SceneViewCamera | null>(null);
const [materialsOverlay, materialsApi] = useMaterialsOverlayState();
@@ -1155,6 +1156,27 @@ export function ControlApp() {
};
}, [npcMapSpawnDrag.dragging, npcMapSpawnDragApi, snapNormActive]);
useEffect(() => {
return api.on(ipcChannels.windows.closeMultiWindowConfirmRequested, () => {
setStopConfirmOpen(true);
});
}, [api]);
useEffect(() => {
if (npcMapSpawnDrag.dragging) return;
const onKey = (e: KeyboardEvent) => {
if (e.key !== 'Escape') return;
e.preventDefault();
if (stopConfirmOpen) {
setStopConfirmOpen(false);
return;
}
setStopConfirmOpen(true);
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [npcMapSpawnDrag.dragging, stopConfirmOpen]);
useEffect(() => {
if (!markersInteractive) {
setNpcSessionCtxMenu(null);
@@ -3670,6 +3692,46 @@ export function ControlApp() {
document.body,
)
: null}
{stopConfirmOpen
? createPortal(
<>
<button
type="button"
aria-label={t('common.close')}
className={styles.modalBackdrop}
onClick={() => setStopConfirmOpen(false)}
/>
<div role="dialog" aria-modal="true" className={styles.modalDialog} data-testid="stop-confirm-modal">
<div className={styles.modalHeader}>
<div className={styles.modalTitle}>{t('control.stopConfirm.title')}</div>
<button
type="button"
aria-label={t('common.close')}
className={styles.modalClose}
onClick={() => setStopConfirmOpen(false)}
>
×
</button>
</div>
<div>{t('control.stopConfirm.body')}</div>
<div className={styles.modalFooter}>
<Button onClick={() => setStopConfirmOpen(false)}>{t('common.cancel')}</Button>
<Button
variant="primary"
data-testid="stop-confirm-accept"
onClick={() => {
setStopConfirmOpen(false);
void api.invoke(ipcChannels.windows.closeMultiWindow, {});
}}
>
{t('control.stopConfirm.confirm')}
</Button>
</div>
</div>
</>,
document.body,
)
: null}
</div>
);
}
@@ -316,3 +316,13 @@ void test('ControlApp: загрузка камп. аудио — useEffect за
assert.ok(!/\bproject\b/.test(depList));
assert.ok(!/\bcampaignAudioRefs\b/.test(depList));
});
void test('ControlApp: Escape → своя модалка подтверждения завершения показа', () => {
const src = readControlApp();
assert.ok(src.includes('closeMultiWindowConfirmRequested'));
assert.ok(src.includes('stopConfirmOpen'));
assert.ok(src.includes('control.stopConfirm.title'));
assert.ok(src.includes('data-testid="stop-confirm-modal"'));
assert.ok(!src.includes('showMessageBox'));
assert.ok(!src.includes('window.confirm'));
});