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.filePath.val +
(editor.val.file.isDirty() ? "*" : ""), (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), v.div({ class: "flex-auto h-4" }, editor.val.dom),
); );

View File

@ -26,6 +26,7 @@ export class OpenFile {
effects: [StateEffect.appendConfig.of([history()])], effects: [StateEffect.appendConfig.of([history()])],
}).state, }).state,
); );
this.lastSaved = van.state(this.rootState.val.doc);
} }
static async openFile(filePath?: string): Promise<OpenFile> { static async openFile(filePath?: string): Promise<OpenFile> {
@ -34,7 +35,6 @@ export class OpenFile {
} }
const { content, path } = await window.electronAPI.readFile(filePath); const { content, path } = await window.electronAPI.readFile(filePath);
const file = new OpenFile({ doc: content }); const file = new OpenFile({ doc: content });
file.lastSaved = van.state(file.rootState.val.doc);
file.setPath(path); file.setPath(path);
return file; return file;
} }
@ -73,6 +73,53 @@ export class OpenFile {
return editor; 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) { dispatch(trs: TransactionSpec, origin?: Editor) {
const transaction = this.rootState.val.update(trs); const transaction = this.rootState.val.update(trs);
this.rootState.val = transaction.state; this.rootState.val = transaction.state;

View File

@ -278,3 +278,20 @@ export function getCurrentWorkspace(): { root: string | null } {
export function getOpenedFiles(): string[] { export function getOpenedFiles(): string[] {
return Array.from(openedFiles); 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, // handleCreateFile,
getCurrentWorkspace, getCurrentWorkspace,
getOpenedFiles, getOpenedFiles,
showConfirmDialog,
} from "./fileOperations"; } from "./fileOperations";
import path from "node:path"; import path from "node:path";
import started from "electron-squirrel-startup"; import started from "electron-squirrel-startup";
@ -79,6 +80,19 @@ app.whenReady().then(() => {
return getOpenedFiles(); 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(); createWindow();
if (process.platform === "darwin") { if (process.platform === "darwin") {
app.on("activate", function () { app.on("activate", function () {

View File

@ -35,4 +35,12 @@ contextBridge.exposeInMainWorld("electronAPI", {
getOpenedFiles: () => getOpenedFiles: () =>
ipcRenderer.invoke("workspace:getOpenedFiles") as Promise<string[]>, 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 // Workspace info
getCurrentWorkspace: () => Promise<{ root: string | null }>; getCurrentWorkspace: () => Promise<{ root: string | null }>;
getOpenedFiles: () => Promise<string[]>; getOpenedFiles: () => Promise<string[]>;
// Dialog operations
showConfirmDialog: (
message: string,
title: string,
buttons: string[],
) => Promise<string>;
}; };
} }
} }