diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index f7e221e1..46d6923a 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -1,21 +1,26 @@ import { Box, IconButton } from '@radix-ui/themes' import { css } from '@emotion/react' import { Allotment } from 'allotment' -import { Outlet } from 'react-router-dom' +import { Outlet, useLocation } from 'react-router-dom' import { Sidebar } from './Sidebar' import { ActivityBar } from './ActivityBar' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { PinRightIcon } from '@radix-ui/react-icons' export function Layout() { const [isSidebarExpanded, setIsSidebarExpanded] = useState(true) + const location = useLocation() const handleVisibleChange = (index: number, visible: boolean) => { if (index !== 1) return setIsSidebarExpanded(visible) } + useEffect(() => { + window.studio.app.changeRoute(location.pathname) + }, [location]) + return ( { mainWindow.once('ready-to-show', () => { configureApplicationMenu() configureWatcher(mainWindow) + wasAppClosedByClient = false }) proxyEmitter.on('status:change', (statusName: ProxyStatus) => { proxyStatus = statusName @@ -181,6 +184,12 @@ const createWindow = async () => { mainWindow.on('move', () => trackWindowState(mainWindow)) mainWindow.on('resize', () => trackWindowState(mainWindow)) + mainWindow.on('close', (event) => { + mainWindow.webContents.send('app:close') + if (currentClientRoute.startsWith('/generator') && !wasAppClosedByClient) { + event.preventDefault() + } + }) return mainWindow } @@ -218,6 +227,22 @@ app.on('before-quit', async () => { stopProxyProcess() }) +ipcMain.handle('app:change-route', async (_, route: string) => { + currentClientRoute = route +}) + +ipcMain.handle('app:close', (event) => { + console.log('app:close event received') + + wasAppClosedByClient = true + if (appShuttingDown) { + app.quit() + return + } + const browserWindow = browserWindowFromEvent(event) + browserWindow.close() +}) + // Proxy ipcMain.handle('proxy:start', async (event) => { console.info('proxy:start event received') diff --git a/src/preload.ts b/src/preload.ts index 76f73874..cd8b97af 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -152,6 +152,15 @@ const app = { openApplicationLog: () => { ipcRenderer.invoke('app:open-log') }, + onApplicationClose: (callback: () => void) => { + return createListener('app:close', callback) + }, + closeApplication: () => { + ipcRenderer.invoke('app:close') + }, + changeRoute: (route: string) => { + return ipcRenderer.invoke('app:change-route', route) + }, } as const const settings = { diff --git a/src/views/Generator/Generator.tsx b/src/views/Generator/Generator.tsx index edb30edc..e7108b5a 100644 --- a/src/views/Generator/Generator.tsx +++ b/src/views/Generator/Generator.tsx @@ -1,5 +1,5 @@ import { Allotment } from 'allotment' -import { useEffect } from 'react' +import { useEffect, useState } from 'react' import { useBlocker, useNavigate } from 'react-router-dom' import { Box, ScrollArea } from '@radix-ui/themes' @@ -54,6 +54,8 @@ export function Generator() { const isDirty = useIsGeneratorDirty(fileName) + const [isAppClosing, setIsAppClosing] = useState(false) + const blocker = useBlocker(({ historyAction }) => { // Don't block navigation when redirecting home from invalid generator return isDirty && historyAction !== 'REPLACE' @@ -86,11 +88,43 @@ export function Generator() { } }, [harError, showToast]) + useEffect(() => { + return window.studio.app.onApplicationClose(() => { + if (isDirty || blocker.state === 'blocked') { + setIsAppClosing(true) + return + } + window.studio.app.closeApplication() + }) + }) + const handleSaveGenerator = () => { const generator = selectGeneratorData(useGeneratorStore.getState()) return saveGenerator(generator) } + const handleSaveGeneratorDialog = async () => { + await handleSaveGenerator() + if (isAppClosing) { + return window.studio.app.closeApplication() + } + blocker.proceed?.() + } + + const handleDiscardGeneratorDialog = () => { + if (isAppClosing) { + return window.studio.app.closeApplication() + } + blocker.proceed?.() + } + + const handleCancelGeneratorDialog = () => { + if (isAppClosing) { + return window.studio.app.closeApplication() + } + blocker.reset?.() + } + return ( { - handleSaveGenerator().then(() => blocker.proceed?.()) - }} - onDiscard={() => blocker.proceed?.()} - onCancel={() => blocker.reset?.()} + open={blocker.state === 'blocked' || (isAppClosing && isDirty)} + onSave={handleSaveGeneratorDialog} + onDiscard={handleDiscardGeneratorDialog} + onCancel={handleCancelGeneratorDialog} /> ) diff --git a/src/views/Generator/UnsavedChangesDialog.tsx b/src/views/Generator/UnsavedChangesDialog.tsx index ea498012..e8669d8f 100644 --- a/src/views/Generator/UnsavedChangesDialog.tsx +++ b/src/views/Generator/UnsavedChangesDialog.tsx @@ -27,7 +27,7 @@ export function UnsavedChangesDialog({ You have unsaved changes in the generator which will be lost upon - navigation. + leaving.