From e6f81704e4975de50d179fe5b34dd2e0a23eb91e Mon Sep 17 00:00:00 2001 From: roll Date: Tue, 27 Aug 2024 08:50:27 +0100 Subject: [PATCH] Remove revert button (#488) * Moved Publish dialog to the application * Removed "Revert" button * Implemented UnsavedChanges dialog * Added getIsFileOrResourceUpdated * Don't use proxy/almost empty index files * Moved ClickAwayListener to the Application component * Implemented closeDesktopApp * Warn the user about unsaved changes even if the cell editing is not commited (#517) --- client/components/Application/Content.tsx | 15 +++++- client/components/Application/Dialog.tsx | 4 ++ .../Dialogs/CloseWithUnsavedChanges.tsx | 29 +++++++++++ .../Application/Dialogs/UnsavedChanges.tsx | 28 +++++++++++ client/components/Application/Layout.tsx | 2 - .../Controllers/Base/Dialogs/Leave.tsx | 20 -------- client/components/Controllers/File/Action.tsx | 1 - client/components/Controllers/File/Dialog.tsx | 10 ---- client/components/Controllers/File/File.tsx | 40 --------------- client/components/Controllers/File/index.tsx | 29 ++++++++++- .../components/Controllers/Table/Action.tsx | 1 - .../components/Controllers/Table/Dialog.tsx | 13 ----- .../Controllers/Table/Dialogs/Leave.tsx | 6 --- .../components/Controllers/Table/Editor.tsx | 2 + client/components/Controllers/Table/Table.tsx | 36 ------------- client/components/Controllers/Table/index.tsx | 24 ++++++++- client/components/Controllers/Text/Action.tsx | 1 - client/components/Controllers/Text/Dialog.tsx | 13 ----- .../Controllers/Text/Dialogs/Leave.tsx | 6 --- client/components/Controllers/Text/Text.tsx | 39 --------------- client/components/Controllers/Text/index.tsx | 28 ++++++++++- client/components/Parts/Dialogs/Confirm.tsx | 10 +++- client/store/actions/app.ts | 25 ++++++++++ client/store/actions/dialog.ts | 3 +- client/store/actions/file.ts | 50 ++++++++++++++++--- client/store/actions/table.ts | 10 ---- client/store/actions/text.ts | 10 ---- client/store/state.ts | 8 ++- desktop/bridge.ts | 5 ++ desktop/preload/index.ts | 4 +- 30 files changed, 249 insertions(+), 223 deletions(-) create mode 100644 client/components/Application/Dialogs/CloseWithUnsavedChanges.tsx create mode 100644 client/components/Application/Dialogs/UnsavedChanges.tsx delete mode 100644 client/components/Controllers/Base/Dialogs/Leave.tsx delete mode 100644 client/components/Controllers/File/Dialog.tsx delete mode 100644 client/components/Controllers/File/File.tsx delete mode 100644 client/components/Controllers/Table/Dialog.tsx delete mode 100644 client/components/Controllers/Table/Dialogs/Leave.tsx delete mode 100644 client/components/Controllers/Table/Table.tsx delete mode 100644 client/components/Controllers/Text/Dialog.tsx delete mode 100644 client/components/Controllers/Text/Dialogs/Leave.tsx delete mode 100644 client/components/Controllers/Text/Text.tsx diff --git a/client/components/Application/Content.tsx b/client/components/Application/Content.tsx index 82089b20b..ead194903 100644 --- a/client/components/Application/Content.tsx +++ b/client/components/Application/Content.tsx @@ -1,4 +1,5 @@ import * as React from 'react' +import { ClickAwayListener } from '@mui/base' import Box from '@mui/material/Box' import { ErrorBoundary } from 'react-error-boundary' import File from '../Controllers/File' @@ -6,7 +7,6 @@ import Table from '../Controllers/Table' import Text from '../Controllers/Text' import SpinnerCard from '../Parts/Cards/Spinner' import * as store from '@client/store' -import { client } from '@client/client' import { styled } from '@mui/material/styles' import Typography from '@mui/material/Typography' import CardContent from '@mui/material/CardContent' @@ -46,7 +46,18 @@ function FileContent() { } > - + { + event.preventDefault() + store.onFileLeave() + }} + > + + + + ) } diff --git a/client/components/Application/Dialog.tsx b/client/components/Application/Dialog.tsx index 8a1921602..5938afdf4 100644 --- a/client/components/Application/Dialog.tsx +++ b/client/components/Application/Dialog.tsx @@ -1,6 +1,7 @@ import AddRemoteFileDialog from './Dialogs/AddRemoteFile' import AddEmptyFolderDialog from './Dialogs/AddEmptyFolder' import AdjustFileDialog from './Dialogs/AdjustFile' +import CloseWithUnsavedChangesDialog from './Dialogs/CloseWithUnsavedChanges' import ConfigDialog from './Dialogs/Config' import CopyFileDialog from './Dialogs/CopyFile' import CopyFolderDialog from './Dialogs/CopyFolder' @@ -10,6 +11,7 @@ import IndexFilesDialog from './Dialogs/IndexFiles' import MoveFileDialog from './Dialogs/MoveFile' import MoveFolderDialog from './Dialogs/MoveFolder' import PublishDialog from './Dialogs/Publish' +import UnsavedChangesDialog from './Dialogs/UnsavedChanges' import WelcomeBannerDialog from './Dialogs/WelcomeBanner' import FileUploadDialog from './Dialogs/FileUpload' import * as store from '@client/store' @@ -29,6 +31,7 @@ const DIALOGS = { addEmptyFolder: AddEmptyFolderDialog, addRemoteFile: AddRemoteFileDialog, adjustFile: AdjustFileDialog, + closeWithUnsavedChanges: CloseWithUnsavedChangesDialog, config: ConfigDialog, configProject: ConfigDialog, copyFile: CopyFileDialog, @@ -39,6 +42,7 @@ const DIALOGS = { moveFile: MoveFileDialog, moveFolder: MoveFolderDialog, publish: PublishDialog, + unsavedChanges: UnsavedChangesDialog, welcomeBanner: WelcomeBannerDialog, fileUpload: FileUploadDialog, } as const diff --git a/client/components/Application/Dialogs/CloseWithUnsavedChanges.tsx b/client/components/Application/Dialogs/CloseWithUnsavedChanges.tsx new file mode 100644 index 000000000..f87bba4fe --- /dev/null +++ b/client/components/Application/Dialogs/CloseWithUnsavedChanges.tsx @@ -0,0 +1,29 @@ +import DangerousIcon from '@mui/icons-material/Dangerous' +import ConfirmDialog from '../../Parts/Dialogs/Confirm' +import * as store from '@client/store' + +export default function CloseWithUnsavedChangesDialog() { + const onSave = async () => { + await store.saveFile() + store.closeDesktopApp() + } + + const onDiscard = async () => { + await store.revertFile() + store.closeDesktopApp() + } + + return ( + + ) +} diff --git a/client/components/Application/Dialogs/UnsavedChanges.tsx b/client/components/Application/Dialogs/UnsavedChanges.tsx new file mode 100644 index 000000000..75361f41f --- /dev/null +++ b/client/components/Application/Dialogs/UnsavedChanges.tsx @@ -0,0 +1,28 @@ +import DangerousIcon from '@mui/icons-material/Dangerous' +import ConfirmDialog from '../../Parts/Dialogs/Confirm' +import * as store from '@client/store' + +export default function UnsavedChangesDialog() { + const onSave = async () => { + await store.saveFile() + store.closeDialog() + } + + const onDiscard = async () => { + await store.revertFile() + store.closeDialog() + } + + return ( + + ) +} diff --git a/client/components/Application/Layout.tsx b/client/components/Application/Layout.tsx index cf1b28cee..69175e471 100644 --- a/client/components/Application/Layout.tsx +++ b/client/components/Application/Layout.tsx @@ -11,8 +11,6 @@ export default function Layout() { store.onAppStart().catch(console.error) }, []) - store.openDialog('welcomeBanner') - return ( diff --git a/client/components/Controllers/Base/Dialogs/Leave.tsx b/client/components/Controllers/Base/Dialogs/Leave.tsx deleted file mode 100644 index d9bd22d90..000000000 --- a/client/components/Controllers/Base/Dialogs/Leave.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import DangerousIcon from '@mui/icons-material/Dangerous' -import NoteDialog from '../../../Parts/Dialogs/Note' - -export interface LeaveDialogProps { - onClose: () => void -} - -export default function LeaveDialog(props: LeaveDialogProps) { - return ( - - ) -} diff --git a/client/components/Controllers/File/Action.tsx b/client/components/Controllers/File/Action.tsx index e1cbc7c99..19b091a24 100644 --- a/client/components/Controllers/File/Action.tsx +++ b/client/components/Controllers/File/Action.tsx @@ -4,7 +4,6 @@ export default function Action() { return ( - ) diff --git a/client/components/Controllers/File/Dialog.tsx b/client/components/Controllers/File/Dialog.tsx deleted file mode 100644 index 9707815c9..000000000 --- a/client/components/Controllers/File/Dialog.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import * as store from '@client/store' - -export default function Dialog() { - const dialog = store.useStore((state) => state.dialog) - - switch (dialog) { - default: - return null - } -} diff --git a/client/components/Controllers/File/File.tsx b/client/components/Controllers/File/File.tsx deleted file mode 100644 index 8053f64cf..000000000 --- a/client/components/Controllers/File/File.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import * as React from 'react' -import { ClickAwayListener } from '@mui/base' -import Box from '@mui/material/Box' -import { useTheme } from '@mui/material/styles' -import ScrollBox from '../../Parts/Boxes/Scroll' -import Action from './Action' -import Dialog from './Dialog' -import Menu from './Menu' -import Panel from './Panel' -import View from './View' -import * as store from '@client/store' - -export default function File() { - const theme = useTheme() - const panel = store.useStore((state) => state.panel) - - const height = `calc(100vh - ${theme.spacing(8)})` - const panelHeight = panel ? 42 : 0 - const contentHeight = `calc(100vh - ${theme.spacing(8 + 8 + 8 + panelHeight)})` - - return ( - - - - - - - - - - - - - - ) -} diff --git a/client/components/Controllers/File/index.tsx b/client/components/Controllers/File/index.tsx index d1590f6b8..64992af17 100644 --- a/client/components/Controllers/File/index.tsx +++ b/client/components/Controllers/File/index.tsx @@ -1 +1,28 @@ -export { default } from './File' +import Box from '@mui/material/Box' +import { useTheme } from '@mui/material/styles' +import ScrollBox from '../../Parts/Boxes/Scroll' +import Action from './Action' +import Menu from './Menu' +import Panel from './Panel' +import View from './View' +import * as store from '@client/store' + +export default function File() { + const theme = useTheme() + const panel = store.useStore((state) => state.panel) + + const height = `calc(100vh - ${theme.spacing(8)})` + const panelHeight = panel ? 42 : 0 + const contentHeight = `calc(100vh - ${theme.spacing(8 + 8 + 8 + panelHeight)})` + + return ( + + + + + + + + + ) +} diff --git a/client/components/Controllers/Table/Action.tsx b/client/components/Controllers/Table/Action.tsx index 312d61857..1562496dc 100644 --- a/client/components/Controllers/Table/Action.tsx +++ b/client/components/Controllers/Table/Action.tsx @@ -10,7 +10,6 @@ export default function Action() { disabled={isUpdated} onClick={() => store.openDialog('publish')} /> - ) diff --git a/client/components/Controllers/Table/Dialog.tsx b/client/components/Controllers/Table/Dialog.tsx deleted file mode 100644 index caab54c1a..000000000 --- a/client/components/Controllers/Table/Dialog.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import LeaveDialog from './Dialogs/Leave' -import * as store from '@client/store' - -export default function Dialog() { - const dialog = store.useStore((state) => state.dialog) - - switch (dialog) { - case 'leave': - return - default: - return null - } -} diff --git a/client/components/Controllers/Table/Dialogs/Leave.tsx b/client/components/Controllers/Table/Dialogs/Leave.tsx deleted file mode 100644 index e7a4717d7..000000000 --- a/client/components/Controllers/Table/Dialogs/Leave.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import LeaveDialog from '../../Base/Dialogs/Leave' -import * as store from '@client/store' - -export default function Leave() { - return -} diff --git a/client/components/Controllers/Table/Editor.tsx b/client/components/Controllers/Table/Editor.tsx index 93203031b..55ae6e9a5 100644 --- a/client/components/Controllers/Table/Editor.tsx +++ b/client/components/Controllers/Table/Editor.tsx @@ -21,7 +21,9 @@ export default function Editor() { // Ensure that when the user interact with other parts on the application // e.g. Metadata editor the selection logic is not activated + // also commit current table editing (https://github.com/okfn/opendataeditor/issues/495) const onClickAway = () => { + store.stopTableEditing() setCellSelection({}) } diff --git a/client/components/Controllers/Table/Table.tsx b/client/components/Controllers/Table/Table.tsx deleted file mode 100644 index d8d61e59b..000000000 --- a/client/components/Controllers/Table/Table.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import * as React from 'react' -import { ClickAwayListener } from '@mui/base' -import Box from '@mui/material/Box' -import ScrollBox from '../../Parts/Boxes/Scroll' -import { useTheme } from '@mui/material/styles' -import Action from './Action' -import Editor from './Editor' -import Menu from './Menu' -import Panel from './Panel' -import Dialog from './Dialog' -import * as store from '@client/store' - -export default function Table() { - const theme = useTheme() - const contentHeight = `calc(100vh - ${theme.spacing(8 + 8)})` - - return ( - - - - - - - - - - - - - - ) -} diff --git a/client/components/Controllers/Table/index.tsx b/client/components/Controllers/Table/index.tsx index 74a0ea715..75d8ec3bc 100644 --- a/client/components/Controllers/Table/index.tsx +++ b/client/components/Controllers/Table/index.tsx @@ -1 +1,23 @@ -export { default } from './Table' +import Box from '@mui/material/Box' +import ScrollBox from '../../Parts/Boxes/Scroll' +import { useTheme } from '@mui/material/styles' +import Action from './Action' +import Editor from './Editor' +import Menu from './Menu' +import Panel from './Panel' + +export default function Table() { + const theme = useTheme() + const contentHeight = `calc(100vh - ${theme.spacing(8 + 8)})` + + return ( + + + + + + + + + ) +} diff --git a/client/components/Controllers/Text/Action.tsx b/client/components/Controllers/Text/Action.tsx index 4b0af4632..6c973117f 100644 --- a/client/components/Controllers/Text/Action.tsx +++ b/client/components/Controllers/Text/Action.tsx @@ -7,7 +7,6 @@ export default function Action() { return ( - ) diff --git a/client/components/Controllers/Text/Dialog.tsx b/client/components/Controllers/Text/Dialog.tsx deleted file mode 100644 index caab54c1a..000000000 --- a/client/components/Controllers/Text/Dialog.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import LeaveDialog from './Dialogs/Leave' -import * as store from '@client/store' - -export default function Dialog() { - const dialog = store.useStore((state) => state.dialog) - - switch (dialog) { - case 'leave': - return - default: - return null - } -} diff --git a/client/components/Controllers/Text/Dialogs/Leave.tsx b/client/components/Controllers/Text/Dialogs/Leave.tsx deleted file mode 100644 index e7a4717d7..000000000 --- a/client/components/Controllers/Text/Dialogs/Leave.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import LeaveDialog from '../../Base/Dialogs/Leave' -import * as store from '@client/store' - -export default function Leave() { - return -} diff --git a/client/components/Controllers/Text/Text.tsx b/client/components/Controllers/Text/Text.tsx deleted file mode 100644 index 12c6d61d2..000000000 --- a/client/components/Controllers/Text/Text.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as React from 'react' -import { ClickAwayListener } from '@mui/base' -import { useTheme } from '@mui/material/styles' -import Box from '@mui/material/Box' -import Action from './Action' -import Editor from './Editor' -import Dialog from './Dialog' -import Menu from './Menu' -import Panel from './Panel' -import * as store from '@client/store' - -export default function Text() { - const theme = useTheme() - const panel = store.useStore((state) => state.panel) - - const height = `calc(100vh - ${theme.spacing(8)})` - const panelHeight = panel ? 42 : 0 - const contentHeight = `calc(100vh - ${theme.spacing(8 + 8 + 8 + panelHeight)})` - - return ( - - - - - - - - - - - - - - ) -} diff --git a/client/components/Controllers/Text/index.tsx b/client/components/Controllers/Text/index.tsx index e1e5fa74e..ea75dd4d0 100644 --- a/client/components/Controllers/Text/index.tsx +++ b/client/components/Controllers/Text/index.tsx @@ -1 +1,27 @@ -export { default } from './Text' +import { useTheme } from '@mui/material/styles' +import Box from '@mui/material/Box' +import Action from './Action' +import Editor from './Editor' +import Menu from './Menu' +import Panel from './Panel' +import * as store from '@client/store' + +export default function Text() { + const theme = useTheme() + const panel = store.useStore((state) => state.panel) + + const height = `calc(100vh - ${theme.spacing(8)})` + const panelHeight = panel ? 42 : 0 + const contentHeight = `calc(100vh - ${theme.spacing(8 + 8 + 8 + panelHeight)})` + + return ( + + + + + + + + + ) +} diff --git a/client/components/Parts/Dialogs/Confirm.tsx b/client/components/Parts/Dialogs/Confirm.tsx index be07baf98..217aa80ee 100644 --- a/client/components/Parts/Dialogs/Confirm.tsx +++ b/client/components/Parts/Dialogs/Confirm.tsx @@ -21,17 +21,25 @@ export interface ConfirmDialogProps { onConfirm?: () => void ctrlEnter?: boolean children?: React.ReactNode + disableClosing?: boolean } export default function ConfirmDialog(props: ConfirmDialogProps) { const handleCancel = () => props.onCancel && props.onCancel() const handleConfirm = () => props.onConfirm && props.onConfirm() + + const handleClose = () => { + if (props.loading) return + if (props.disableClosing) return + handleCancel() + } + return ( { diff --git a/client/store/actions/app.ts b/client/store/actions/app.ts index 998d52e59..ff76e03d9 100644 --- a/client/store/actions/app.ts +++ b/client/store/actions/app.ts @@ -1,6 +1,8 @@ import * as store from '../store' import isEqual from 'fast-deep-equal' import delay from 'delay' +import { openDialog } from './dialog' +import { getIsFileOrResourceUpdated } from './file' import * as settings from '@client/settings' import { client } from '@client/client' import { loadConfig } from './config' @@ -9,11 +11,16 @@ import { loadFiles } from './file' export async function onAppStart() { // @ts-ignore const sendFatalError = window?.opendataeditor?.sendFatalError + let ready = false let attempt = 0 const maxAttempts = sendFatalError ? 300 : 3 const delaySeconds = 1 + // Note: it is not possible to use imperative code mutating state + // inside React rendering functions (moved here from the Layout component) + openDialog('welcomeBanner') + while (!ready) { try { await loadConfig(true) @@ -48,4 +55,22 @@ export async function onAppStart() { }) } }, settings.PROJECT_SYNC_INTERVAL_MILLIS) + + // Register on windows close event handler (only Desktop env) + // to prevent closing the app when there are unsaved changes + // @ts-ignore + if (window?.opendataeditor?.closeDesktopApp) { + window.onbeforeunload = (event) => { + const isUpdated = getIsFileOrResourceUpdated(store.getState()) + if (isUpdated) { + event.preventDefault() + openDialog('closeWithUnsavedChanges') + } + } + } +} + +export function closeDesktopApp() { + // @ts-ignore + window?.opendataeditor?.closeDesktopApp() } diff --git a/client/store/actions/dialog.ts b/client/store/actions/dialog.ts index 0039ee03c..786fb92c9 100644 --- a/client/store/actions/dialog.ts +++ b/client/store/actions/dialog.ts @@ -18,6 +18,7 @@ export function openDialog(dialog: IDialog) { export function closeDialog() { store.setState('close-dialog', (state) => { - state.dialog = undefined + state.dialog = state.nextDialog + state.nextDialog = undefined }) } diff --git a/client/store/actions/file.ts b/client/store/actions/file.ts index e9634ff0f..627fa68d5 100644 --- a/client/store/actions/file.ts +++ b/client/store/actions/file.ts @@ -1,10 +1,11 @@ import * as store from '../store' +import invariant from 'tiny-invariant' import { client } from '@client/client' -import { openText, closeText } from './text' +import { openText, closeText, saveText, revertText, getIsTextUpdated } from './text' import { loadSource } from './source' import { cloneDeep } from 'lodash' import { openDialog } from './dialog' -import { openTable, closeTable } from './table' +import { openTable, closeTable, saveTable, revertTable, getIsTableUpdated } from './table' import { emitEvent } from './event' import * as helpers from '@client/helpers' import * as settings from '@client/settings' @@ -173,6 +174,28 @@ export async function copyFile(path: string, toPath: string) { await onFileCreated([result.path]) } +export async function saveFile() { + const { record } = store.getState() + invariant(record) + + if (record.type === 'table') { + await saveTable() + } else if (record.type === 'text') { + await saveText() + } +} + +export async function revertFile() { + const { record } = store.getState() + invariant(record) + + if (record.type === 'table') { + await revertTable() + } else if (record.type === 'text') { + await revertText() + } +} + export async function deleteFiles(paths: string[]) { for (const path of paths) { const result = await client.fileDelete({ path }) @@ -248,11 +271,12 @@ export async function onFileDeleted(paths: string[]) { await loadFiles() } -export function onFileClickAway() { - const { dialog, isResourceUpdated } = store.getState() +export function onFileLeave() { + const { dialog } = store.getState() + const isUpdated = getIsFileOrResourceUpdated(store.getState()) - if (isResourceUpdated && !dialog) { - openDialog('leave') + if (isUpdated && !dialog) { + openDialog('unsavedChanges') } } @@ -271,3 +295,17 @@ export const getFolderPath = store.createSelector((state) => { export const getNotIndexedFiles = store.createSelector((state) => { return state.files.filter((file) => !file.name) }) + +export const getIsFileOrResourceUpdated = store.createSelector((state) => { + return getIsFileUpdated(state) || state.isResourceUpdated +}) + +export const getIsFileUpdated = store.createSelector((state) => { + if (state.record?.type === 'table') { + return getIsTableUpdated(state) + } else if (state.record?.type === 'text') { + return getIsTextUpdated(state) + } else { + return false + } +}) diff --git a/client/store/actions/table.ts b/client/store/actions/table.ts index a2e91d976..8ccde6491 100644 --- a/client/store/actions/table.ts +++ b/client/store/actions/table.ts @@ -1,7 +1,6 @@ import { client } from '@client/client' import { mapValues, isNull } from 'lodash' import { onFileCreated, onFileUpdated } from './file' -import { openDialog } from './dialog' import { cloneDeep } from 'lodash' import { revertResource } from './resource' import { getRefs } from './refs' @@ -162,15 +161,6 @@ export async function toggleTableErrorMode() { grid.reload() } -export function onTableClickAway() { - const { dialog } = store.getState() - const isUpdated = getIsTableOrResourceUpdated(store.getState()) - - if (isUpdated && !dialog) { - openDialog('leave') - } -} - export function startTableEditing(context: any) { store.setState('start-table-editing', (state) => { state.table!.initialEditingValue = context.value diff --git a/client/store/actions/text.ts b/client/store/actions/text.ts index fad826f3f..cb3b59bb1 100644 --- a/client/store/actions/text.ts +++ b/client/store/actions/text.ts @@ -1,6 +1,5 @@ import { client } from '@client/client' import { getRefs } from './refs' -import { openDialog } from './dialog' import { getLanguageByFormat } from '@client/helpers' import { onFileCreated, onFileUpdated } from './file' import { revertResource } from './resource' @@ -167,15 +166,6 @@ export function prettifyJson() { action.run() } -export function onTextClickAway() { - const { dialog } = store.getState() - const isUpdated = getIsTextOrResourceUpdated(store.getState()) - - if (isUpdated && !dialog) { - openDialog('leave') - } -} - // Selectors export const getIsTextOrResourceUpdated = store.createSelector((state) => { diff --git a/client/store/state.ts b/client/store/state.ts index 3e6a63758..9fbecac43 100644 --- a/client/store/state.ts +++ b/client/store/state.ts @@ -73,6 +73,11 @@ export type IState = { **/ dialog?: IDialog + /** + * Will be opened when the main `dialog` is closed (for dialog chains) + **/ + nextDialog?: IDialog + /** * Keeps track of the selected panel **/ @@ -168,7 +173,8 @@ export type IDialog = | 'start' | 'publish' | 'chat' - | 'leave' + | 'unsavedChanges' + | 'closeWithUnsavedChanges' | 'welcomeBanner' | 'fileUpload' diff --git a/desktop/bridge.ts b/desktop/bridge.ts index 6bb38c2d1..1757044b2 100644 --- a/desktop/bridge.ts +++ b/desktop/bridge.ts @@ -12,6 +12,7 @@ export function createBridge() { }) app.quit() }) + ipcMain.handle('openDirectoryDialog', async () => { const { canceled, filePaths } = await dialog.showOpenDialog({ title: 'Select a folder', @@ -23,4 +24,8 @@ export function createBridge() { return filePaths[0] } }) + + ipcMain.handle('closeDesktopApp', async () => { + app.quit() + }) } diff --git a/desktop/preload/index.ts b/desktop/preload/index.ts index f1912d5b3..ef6242861 100644 --- a/desktop/preload/index.ts +++ b/desktop/preload/index.ts @@ -3,5 +3,7 @@ import { contextBridge, ipcRenderer } from 'electron' contextBridge.exposeInMainWorld('opendataeditor', { sendFatalError: (message: string) => ipcRenderer.invoke('sendFatalError', message), openDirectoryDialog: () => ipcRenderer.invoke('openDirectoryDialog'), - ensureLogs: (callback: any) => ipcRenderer.on('ensureLogs', (_event, message: string) => callback(message)) + ensureLogs: (callback: any) => + ipcRenderer.on('ensureLogs', (_event, message: string) => callback(message)), + closeDesktopApp: () => ipcRenderer.invoke('closeDesktopApp'), })