Implement proper file closing

This commit is contained in:
Quinten Kock 2025-11-10 03:54:37 +01:00
parent 6e09e25b2b
commit fd1956dbf6
6 changed files with 106 additions and 2 deletions

View File

@ -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),
);

View File

@ -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<OpenFile> {
@ -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<boolean> {
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<boolean> {
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;

View File

@ -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<string> {
const result = await dialog.showMessageBox(mainWindow, {
type: "question",
buttons,
defaultId: 0,
title,
message,
});
return buttons[result.response];
}

View File

@ -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 () {

View File

@ -35,4 +35,12 @@ contextBridge.exposeInMainWorld("electronAPI", {
getOpenedFiles: () =>
ipcRenderer.invoke("workspace:getOpenedFiles") as Promise<string[]>,
showConfirmDialog: (message: string, title: string, buttons: string[]) =>
ipcRenderer.invoke(
"dialog:confirm",
message,
title,
buttons,
) as Promise<string>,
});

View File

@ -27,6 +27,13 @@ declare global {
// Workspace info
getCurrentWorkspace: () => Promise<{ root: string | null }>;
getOpenedFiles: () => Promise<string[]>;
// Dialog operations
showConfirmDialog: (
message: string,
title: string,
buttons: string[],
) => Promise<string>;
};
}
}