Added confirmations when opening/closing files

If current file has unsaved changes it will ask for permission before opening or closing
This commit is contained in:
Matthew Welch 2021-01-25 20:39:52 -08:00
parent e50248bde6
commit f9f50cd562
4 changed files with 206 additions and 33 deletions

@ -80,6 +80,48 @@
</div> </div>
</div> </div>
<div class="modal fade" id="open-file" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<h5>Are you sure that you want to open a file? You will lose unsaved data.</h5>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No, stay here</button>
<button id="confirm-open-file" type="button" class="btn btn-danger" data-bs-dismiss="modal">Yes, open file</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="start-new" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<h5>Are you sure that you want to start a new file? You will lose unsaved data.</h5>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No, stay here</button>
<button id="confirm-start-new" type="button" class="btn btn-danger" data-bs-dismiss="modal">Yes, start new</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="quit" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<h5>Are you sure that you want to quit? You will lose unsaved data.</h5>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No, stay open</button>
<button id="confirm-quit" type="button" class="btn btn-danger" data-bs-dismiss="modal">Yes, quit</button>
</div>
</div>
</div>
</div>
<script> <script>
if (typeof module === 'object') { if (typeof module === 'object') {
window.module = module; window.module = module;

@ -31,7 +31,6 @@ let schema_updaters = {
log.transports.file.resolvePath = () => path.join(app.getAppPath(), "debug.log"); log.transports.file.resolvePath = () => path.join(app.getAppPath(), "debug.log");
const store = new Store(); const store = new Store();
let current_file = null;
let debug = false; let debug = false;
let menu_template:MenuItemConstructorOptions[] = [ let menu_template:MenuItemConstructorOptions[] = [
{ {
@ -51,11 +50,18 @@ let menu_template:MenuItemConstructorOptions[] = [
window.webContents.send("save", true); window.webContents.send("save", true);
} }
}, },
{
label: "New File",
accelerator: "Ctrl+N",
click: function (item, window, event) {
window.webContents.send("try-start-new");
}
},
{ {
label: "Open", label: "Open",
accelerator: "Ctrl+O", accelerator: "Ctrl+O",
click: function () { click: function (item, window, event) {
openFile(); window.webContents.send("try-open");
} }
}, },
{ {
@ -64,8 +70,8 @@ let menu_template:MenuItemConstructorOptions[] = [
}, },
{ {
label: "Exit", label: "Exit",
click: function () { click: function (item, window, event) {
app.quit(); window.webContents.send("try-quit");
} }
} }
] ]
@ -125,7 +131,7 @@ app.on('ready', start);
// explicitly with Cmd + Q. // explicitly with Cmd + Q.
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
app.quit(); app.exit();
} }
}); });
@ -137,6 +143,11 @@ app.on('activate', () => {
} }
}); });
app.on("before-quit", (event) => {
event.preventDefault();
BrowserWindow.getFocusedWindow().webContents.send("try-quit");
})
function start() { function start() {
if (process.argv.includes("--debug")) { if (process.argv.includes("--debug")) {
debug = true; debug = true;
@ -170,7 +181,7 @@ function createSplashScreen() {
// store.openInEditor(); // store.openInEditor();
} }
function createEditorWindow(json) { function createEditorWindow(file_name, json) {
let main_window = new BrowserWindow({ let main_window = new BrowserWindow({
width: 1000, width: 1000,
height: 600, height: 600,
@ -179,12 +190,16 @@ function createEditorWindow(json) {
nodeIntegration: true nodeIntegration: true
} }
}) })
.on("close", (event) => {
event.preventDefault();
main_window.webContents.send("try-close-window");
})
main_window.maximize(); main_window.maximize();
updateMenuBar(); updateMenuBar();
main_window.loadFile(path.join(app.getAppPath(), "src/editor.html")) main_window.loadFile(path.join(app.getAppPath(), "src/editor.html"))
.then(() => { .then(() => {
main_window.webContents.send("open", json); main_window.webContents.send("open", file_name, json);
}); });
// Open the DevTools. // Open the DevTools.
if (debug) { if (debug) {
@ -196,8 +211,8 @@ function createEditorWindow(json) {
function createNew() { function createNew() {
let old_window = BrowserWindow.getFocusedWindow(); let old_window = BrowserWindow.getFocusedWindow();
let json = loadJson(path.join(app.getAppPath(), "src/default.json")); let json = loadJson(path.join(app.getAppPath(), "src/default.json"));
let main_window = createEditorWindow(json); let main_window = createEditorWindow(null, json);
old_window.close(); old_window.destroy();
} }
function chooseFile(title, for_save=false) { function chooseFile(title, for_save=false) {
@ -234,29 +249,29 @@ function openFile(file_path="", new_file=false) {
if (result) { if (result) {
let json = getUpdatedJson(result); let json = getUpdatedJson(result);
updateRecentFiles(file_path); updateRecentFiles(file_path);
current_file = file_path
if (new_file) { if (new_file) {
let old_window = BrowserWindow.getFocusedWindow(); let old_window = BrowserWindow.getFocusedWindow();
createEditorWindow(json); createEditorWindow(file_path, json);
old_window.close(); old_window.close();
} else { } else {
let window = BrowserWindow.getFocusedWindow(); let window = BrowserWindow.getFocusedWindow();
window.webContents.send("open", json); window.webContents.send("open", file_path, json);
} }
} }
} }
} }
function saveFile(json_data, create_new_file) { function saveFile(file_name, json_data) {
if (current_file == null || create_new_file) { if (file_name == null) {
let file_path = chooseFile("Save website data", true); let file_path = chooseFile("Save website data", true);
if (file_path) { if (file_path) {
updateRecentFiles(file_path); updateRecentFiles(file_path);
current_file = file_path; file_name = file_path;
} }
} }
if (current_file) { if (file_name) {
fs.writeFileSync(current_file, JSON.stringify(json_data, null, 4)); fs.writeFileSync(file_name, JSON.stringify(json_data, null, 4));
BrowserWindow.getFocusedWindow().webContents.send("file-saved", file_name);
} }
} }
@ -292,7 +307,7 @@ function updateMenuBar() {
if (fs.existsSync(file)) { if (fs.existsSync(file)) {
menu.submenu.push({ menu.submenu.push({
label: file, click: () => { label: file, click: () => {
openFile(file); BrowserWindow.getFocusedWindow().webContents.send("try-open", file);
} }
}); });
} }
@ -397,10 +412,18 @@ ipcMain.on("create-new", (event) => {
createNew(); createNew();
}) })
ipcMain.on("save", (event, json_data, create_new_file) => { ipcMain.on("save", (event, file_name, json_data) => {
saveFile(json_data, create_new_file); saveFile(file_name, json_data);
}) })
ipcMain.on("open", (event, file_path="") => { ipcMain.on("open", (event, file_path="", new_file=false) => {
openFile(file_path, true) openFile(file_path, new_file)
})
ipcMain.on("quit", (event) => {
app.exit();
})
ipcMain.on("close-window", (event) => {
BrowserWindow.getFocusedWindow().destroy();
}) })

@ -1,13 +1,15 @@
import {ipcRenderer, clipboard} from 'electron'; import {ipcRenderer, clipboard} from 'electron';
import * as $ from "../../third_party/js/jquery-3.5.1.js"; import * as $ from "../../third_party/js/jquery-3.5.1.js";
import * as Sortable from "../../third_party/js/Sortable.js" import * as Sortable from "../../third_party/js/Sortable.js";
import * as Selectable from "../../third_party/js/selectable.js" import * as Selectable from "../../third_party/js/selectable.js";
import * as util from "util"; import * as util from "util";
let json_data; let json_data;
let tables_data; let tables_data;
let current_table; let current_table;
let is_positioning = false; let is_positioning = false;
let undo_history = {}; let undo_history = {};
let current_file_name = null;
let current_file_modified = false;
let nav = $("nav"); let nav = $("nav");
let tables = $("#tables"); let tables = $("#tables");
let nav_scroll = nav.scrollLeft(); let nav_scroll = nav.scrollLeft();
@ -21,7 +23,9 @@ let detail_column_sortable = $("#detail-column-sortable");
let detail_cell_text = $("#detail-cell-text"); let detail_cell_text = $("#detail-cell-text");
let position_edit_btn = $("#position-edit"); let position_edit_btn = $("#position-edit");
function openFile(json) { function openFile(file_name, json) {
updateSaveStatus(false, file_name);
resetUndoHistory();
json_data = json; json_data = json;
tables_data = json["tables"]; tables_data = json["tables"];
current_table = null; current_table = null;
@ -29,7 +33,11 @@ function openFile(json) {
} }
function saveFile(create_new_file) { function saveFile(create_new_file) {
ipcRenderer.send("save", json_data, create_new_file); if (create_new_file) {
ipcRenderer.send("save", null, json_data);
} else {
ipcRenderer.send("save", current_file_name, json_data);
}
} }
function clearPage() { function clearPage() {
@ -307,6 +315,7 @@ function addTable() {
tables_data.push(table_data); tables_data.push(table_data);
getSelectionData(true); getSelectionData(true);
generateTable(table_data); generateTable(table_data);
updateSaveStatus(true);
} }
function duplicateTable(table_id) { function duplicateTable(table_id) {
@ -327,6 +336,7 @@ function duplicateTable(table_id) {
} }
} }
refreshPage(); refreshPage();
updateSaveStatus(true);
} }
function insertColumn(table_id, index=-1) { function insertColumn(table_id, index=-1) {
@ -459,6 +469,7 @@ function deleteColumn(table_id, col_index) {
} }
} }
refreshPage(); refreshPage();
addUndoHistory(current_table);
} }
function deleteRow(table_id, row_index) { function deleteRow(table_id, row_index) {
@ -469,6 +480,7 @@ function deleteRow(table_id, row_index) {
} }
} }
refreshPage(); refreshPage();
addUndoHistory(current_table);
} }
function cellContextMenu(event, cell_id) { function cellContextMenu(event, cell_id) {
@ -623,6 +635,9 @@ function navContextMenu(event, table_id) {
}) })
menu.remove(); menu.remove();
})); }));
$("#delete-table")[0].addEventListener("hidden.bs.modal", (event) => {
$("#confirm-delete-table").off();
})
} }
let max_height = $(document).height(); let max_height = $(document).height();
let max_width = $(document).width(); let max_width = $(document).width();
@ -640,9 +655,7 @@ function cellInlineEdit(cell_id) {
let target: JQuery = $("#"+cell_id); let target: JQuery = $("#"+cell_id);
let width = target.outerWidth(); let width = target.outerWidth();
let height = target.outerHeight(); let height = target.outerHeight();
console.log(height);
let text_area = $("<textarea>") let text_area = $("<textarea>")
// .css({"width": width, "height": height})
.on("focusout", () => { .on("focusout", () => {
closeCellInlineEdit(true); closeCellInlineEdit(true);
}) })
@ -735,6 +748,7 @@ function moveTable(old_index, new_index) {
tables_data.splice(new_index, 0, table_data); tables_data.splice(new_index, 0, table_data);
getSelectionData(true); getSelectionData(true);
addUndoHistory(current_table); addUndoHistory(current_table);
updateSaveStatus(true);
} }
} }
@ -750,6 +764,7 @@ function moveRow(table_id, old_index, new_index) {
} }
getSelectionData(true); getSelectionData(true);
addUndoHistory(current_table); addUndoHistory(current_table);
updateSaveStatus(true);
} }
} }
@ -765,6 +780,7 @@ function moveCol(table_id, old_index, new_index) {
getSelectionData(true) getSelectionData(true)
addUndoHistory(current_table); addUndoHistory(current_table);
refreshPage(); refreshPage();
updateSaveStatus(true);
} }
} }
@ -1070,6 +1086,7 @@ function addUndoHistory(table_id) {
if (!util.isDeepStrictEqual(tables_data[i], undo_history[table_id]["history"][index])) { if (!util.isDeepStrictEqual(tables_data[i], undo_history[table_id]["history"][index])) {
undo_history[table_id]["history"].splice(0, index, makeCopy(tables_data[i])); undo_history[table_id]["history"].splice(0, index, makeCopy(tables_data[i]));
undo_history[table_id]["index"] = 0; undo_history[table_id]["index"] = 0;
updateSaveStatus(true);
} }
} }
} }
@ -1086,6 +1103,7 @@ function undo() {
} }
} }
refreshPage(); refreshPage();
updateSaveStatus(true);
} }
} }
@ -1100,11 +1118,33 @@ function redo() {
} }
} }
refreshPage(); refreshPage();
updateSaveStatus(true);
} }
} }
function deleteUndoHistory(table_id) { function deleteUndoHistory(table_id) {
delete undo_history[table_id]; delete undo_history[table_id];
updateSaveStatus(true);
}
function resetUndoHistory() {
undo_history = {};
}
function updateSaveStatus(file_modified, new_file_name=null) {
current_file_modified = file_modified;
let file_name = current_file_name;
if (new_file_name != null) {
current_file_name = new_file_name;
file_name = new_file_name;
} else if (file_name == null) {
file_name = "[untitled]"
}
if (current_file_modified) {
$(document).attr("title", "Web Editor - "+file_name+"*");
} else {
$(document).attr("title", "Web Editor - "+file_name);
}
} }
function getColumnIDs(columns): Array<string> { function getColumnIDs(columns): Array<string> {
@ -1276,8 +1316,8 @@ $(document).on("keydown", (event: JQuery.KeyDownEvent) => {
} }
}) })
ipcRenderer.on("open", (event, json) => { ipcRenderer.on("open", (event, file_name, json) => {
openFile(json); openFile(file_name, json);
}) })
ipcRenderer.on("save", (event, create_new_file) => { ipcRenderer.on("save", (event, create_new_file) => {
@ -1302,3 +1342,71 @@ ipcRenderer.on("undo", (event) => {
ipcRenderer.on("redo", (event) => { ipcRenderer.on("redo", (event) => {
redo(); redo();
}) })
ipcRenderer.on("file-saved", (event, file_name) => {
updateSaveStatus(false, file_name);
})
ipcRenderer.on("try-open", (event, file_name) => {
if (current_file_modified) {
$("#confirm-open-file").one("click", () => {
ipcRenderer.send("open", file_name);
})
let modal_el = $("#open-file")
let modal = new bootstrap.Modal(modal_el[0]);
modal.show();
modal_el[0].addEventListener("hidden.bs.modal", (event) => {
$("#confirm-open-file").off();
})
} else {
ipcRenderer.send("open", file_name);
}
})
ipcRenderer.on("try-start-new", (event, file_name) => {
if (current_file_modified) {
$("#confirm-start-new").one("click", () => {
ipcRenderer.send("create-new", file_name);
})
let modal_el = $("#start-new")
let modal = new bootstrap.Modal(modal_el[0]);
modal.show();
modal_el[0].addEventListener("hidden.bs.modal", (event) => {
$("#confirm-start-new").off();
})
} else {
ipcRenderer.send("create-new", file_name);
}
})
ipcRenderer.on("try-quit", (event) => {
if (current_file_modified) {
$("#confirm-quit").one("click", () => {
ipcRenderer.send("quit");
})
let modal_el = $("#quit")
let modal = new bootstrap.Modal(modal_el[0]);
modal.show();
modal_el[0].addEventListener("hidden.bs.modal", (event) => {
$("#confirm-quit").off();
})
} else {
ipcRenderer.send("quit");
}
})
ipcRenderer.on("try-close-window", (event) => {
if (current_file_modified) {
$("#confirm-quit").one("click", () => {
ipcRenderer.send("close-window");
})
let modal_el = $("#quit")
let modal = new bootstrap.Modal(modal_el[0]);
modal.show();
modal_el[0].addEventListener("hidden.bs.modal", (event) => {
$("#confirm-quit").off();
})
} else {
ipcRenderer.send("close-window");
}
})

@ -15,7 +15,7 @@ async function showRecentFiles() {
.text(file.replace(/^.*[\\\/]/, '')) .text(file.replace(/^.*[\\\/]/, ''))
.attr({"data-bs-toggle": "tooltip", "data-bs-placement": "bottom", "title": file}) .attr({"data-bs-toggle": "tooltip", "data-bs-placement": "bottom", "title": file})
.on("click", () => { .on("click", () => {
ipcRenderer.send("open", file); ipcRenderer.send("open", file, true);
}) })
.append($("<a>") .append($("<a>")
.addClass("btn-close") .addClass("btn-close")
@ -40,5 +40,5 @@ $("#create-new").on("click", () => {
}); });
$("#open-file").on("click", () => { $("#open-file").on("click", () => {
ipcRenderer.send("open"); ipcRenderer.send("open", "", true);
}) })