diff --git a/src/app/editorgrid.ts b/src/app/editorgrid.ts index 5d51a6b..e670ad7 100644 --- a/src/app/editorgrid.ts +++ b/src/app/editorgrid.ts @@ -16,7 +16,18 @@ const EditorWrapper = (editor: any, del: any, k: any) => editor.val.file.filePath.val + (editor.val.file.isDirty() ? "*" : ""), ), - u.InlineButton(del, "Close", "❌"), + u.InlineButton( + async () => { + const canClose = await editor.val.file.removeEditor( + editor.val, + ); + if (canClose) { + del(); + } + }, + "Close", + "❌", + ), ), v.div({ class: "flex-auto h-4" }, editor.val.dom), ); diff --git a/src/app/filestate.ts b/src/app/filestate.ts index 5b91fda..5e73bf9 100644 --- a/src/app/filestate.ts +++ b/src/app/filestate.ts @@ -26,6 +26,7 @@ export class OpenFile { effects: [StateEffect.appendConfig.of([history()])], }).state, ); + this.lastSaved = van.state(this.rootState.val.doc); } static async openFile(filePath?: string): Promise { @@ -34,7 +35,6 @@ export class OpenFile { } const { content, path } = await window.electronAPI.readFile(filePath); const file = new OpenFile({ doc: content }); - file.lastSaved = van.state(file.rootState.val.doc); file.setPath(path); return file; } @@ -73,6 +73,53 @@ export class OpenFile { return editor; } + // Function to remove an editor and clean up if no more editors exist + async removeEditor(editor: Editor): Promise { + const index = this.editors.indexOf(editor); + if (index > -1) { + this.editors.splice(index, 1); + } + + // If this is the last editor and the file is dirty, confirm before closing + if (this.editors.length === 0 && this.isDirty()) { + const confirmed = await this.confirmClose(); + if (!confirmed) { + // Re-add the editor if user cancelled + this.editors.push(editor); + return false; + } + } + + // If no more editors, remove from openFiles dictionary + if (this.editors.length === 0) { + delete openFiles[this.filePath.val]; + } + + return true; + } + + // Function to confirm closing of dirty file + private async confirmClose(): Promise { + const fileName = this.filePath.val + ? this.filePath.val.split("/").pop() + : "untitled"; + const message = `Do you want to save the changes to ${fileName}?`; + const result = await window.electronAPI.showConfirmDialog( + message, + "Save Changes?", + ["Save", "Don't Save", "Cancel"], + ); + + if (result === "Save") { + await this.saveFile(); + return true; + } else if (result === "Don't Save") { + return true; + } else { + return false; + } + } + dispatch(trs: TransactionSpec, origin?: Editor) { const transaction = this.rootState.val.update(trs); this.rootState.val = transaction.state; diff --git a/src/main/fileOperations.ts b/src/main/fileOperations.ts index 42aa2ca..e48cf12 100644 --- a/src/main/fileOperations.ts +++ b/src/main/fileOperations.ts @@ -278,3 +278,20 @@ export function getCurrentWorkspace(): { root: string | null } { export function getOpenedFiles(): string[] { return Array.from(openedFiles); } + +// Show confirmation dialog +export async function showConfirmDialog( + mainWindow: BrowserWindow, + message: string, + title: string, + buttons: string[] = ["OK", "Cancel"], +): Promise { + const result = await dialog.showMessageBox(mainWindow, { + type: "question", + buttons, + defaultId: 0, + title, + message, + }); + return buttons[result.response]; +} diff --git a/src/main/main.ts b/src/main/main.ts index 706b3ae..bc3388a 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -6,6 +6,7 @@ import { // handleCreateFile, getCurrentWorkspace, getOpenedFiles, + showConfirmDialog, } from "./fileOperations"; import path from "node:path"; import started from "electron-squirrel-startup"; @@ -79,6 +80,19 @@ app.whenReady().then(() => { return getOpenedFiles(); }); + ipcMain.handle( + "dialog:confirm", + async (event, message: string, title: string, buttons: string[]) => { + const senderWindow = BrowserWindow.fromWebContents(event.sender); + return await showConfirmDialog( + senderWindow, + message, + title, + buttons, + ); + }, + ); + createWindow(); if (process.platform === "darwin") { app.on("activate", function () { diff --git a/src/preload.ts b/src/preload.ts index b8db91d..bf4b3f4 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -35,4 +35,12 @@ contextBridge.exposeInMainWorld("electronAPI", { getOpenedFiles: () => ipcRenderer.invoke("workspace:getOpenedFiles") as Promise, + + showConfirmDialog: (message: string, title: string, buttons: string[]) => + ipcRenderer.invoke( + "dialog:confirm", + message, + title, + buttons, + ) as Promise, }); diff --git a/src/types/global.d.ts b/src/types/global.d.ts index b693c91..1e12101 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -27,6 +27,13 @@ declare global { // Workspace info getCurrentWorkspace: () => Promise<{ root: string | null }>; getOpenedFiles: () => Promise; + + // Dialog operations + showConfirmDialog: ( + message: string, + title: string, + buttons: string[], + ) => Promise; }; } }