import { app, BrowserWindow, ipcMain, dialog, Menu, MenuItemConstructorOptions, SaveDialogSyncOptions, OpenDialogSyncOptions } from 'electron'; import * as Store from "electron-store"; import * as fs from "fs"; import * as path from "path"; import * as util from 'util'; import * as log from "electron-log"; import * as parseArgs from "minimist"; import { Validator } from "jsonschema"; import * as jsdom from "jsdom"; const { JSDOM } = jsdom; const { window } = new JSDOM(); let $ = require("../third_party/js/jquery-3.5.1.js")(window); let validator = new Validator(); let all_schemas = [ path.join(app.getAppPath(), "src/schemas/current_schema.json"), path.join(app.getAppPath(), "src/schemas/1.0.0_schema.json"), ] let schema_updaters = { "1.0.0_schema.json": update_1_0_0_schema } log.transports.file.resolvePath = () => path.join(app.getAppPath(), "debug.log"); const store = new Store(); let debug = false; let menu_template:MenuItemConstructorOptions[] = [ { label: "File", submenu: [ { label: "Save", accelerator: "Ctrl+S", click: function (item, window, event) { window.webContents.send("save", false); } }, { label: "Save As", accelerator: "Ctrl+Shift+S", click: function (item, window, event) { window.webContents.send("save", true); } }, { label: "New File", accelerator: "Ctrl+N", click: function (item, window, event) { window.webContents.send("try-start-new"); } }, { label: "Open", accelerator: "Ctrl+O", click: function (item, window, event) { window.webContents.send("try-open"); } }, { label: "Open Recent", submenu:[] }, { label: "Exit", click: function (item, window, event) { window.webContents.send("try-quit"); } } ] }, { label: "Edit", submenu: [ { label: "Cut", role: "cut", }, { label: "Copy", role: "copy", }, { label: "Paste", role: "paste", }, { label: "Select All", accelerator: "Ctrl+A", click: function (item, window, event) { window.webContents.send("select-all"); } }, { label: "Undo", accelerator: "Ctrl+Z", click: function (item, window, event) { window.webContents.send("undo"); } }, { label: "Redo", accelerator: "Ctrl+Y", click: function (item, window, event) { window.webContents.send("redo"); } } ] }, ] // Handle creating/removing shortcuts on Windows when installing/uninstalling. if (require('electron-squirrel-startup')) { // eslint-disable-line global-require app.quit(); } // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on('ready', start); // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.exit(); } }); app.on('activate', () => { // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) { start(); } }); app.on("before-quit", (event) => { event.preventDefault(); BrowserWindow.getFocusedWindow().webContents.send("try-quit"); }) function start() { let args = parseArgs(process.argv.slice(1)); if (args["_"][0] == ".") { args["_"].splice(0, 1); } if (args["debug"]) { debug = true; } if (args["_"].length > 0) { for (let file_path of args["_"]) { log.info(file_path); openFile(file_path, true, true); } } else { createSplashScreen(); } } function createSplashScreen() { const splash_screen = new BrowserWindow({ width: 806, height: 529, autoHideMenuBar: true, webPreferences: { nodeIntegration: true }, resizable: false, frame: true }); if (!store.has("recent_files")) { store.set("recent_files", []); } else { updateRecentFiles(); } splash_screen.loadFile(path.join(app.getAppPath(), "src/splash.html")); // Open the DevTools. if (debug) { splash_screen.webContents.openDevTools(); } // store.openInEditor(); } function createEditorWindow(file_name, json) { let main_window = new BrowserWindow({ width: 1000, height: 600, autoHideMenuBar: false, webPreferences: { nodeIntegration: true } }) .on("close", (event) => { event.preventDefault(); main_window.webContents.send("try-close-window"); }) main_window.maximize(); updateMenuBar(); main_window.loadFile(path.join(app.getAppPath(), "src/editor.html")) .then(() => { main_window.webContents.send("open", file_name, json); }); // Open the DevTools. if (debug) { main_window.webContents.openDevTools(); } return main_window; } function createNew() { let old_window = BrowserWindow.getFocusedWindow(); let json = loadJson(path.join(app.getAppPath(), "src/default.json")); let main_window = createEditorWindow(null, json); old_window.destroy(); } function chooseFile(title, for_save=false) { let options:SaveDialogSyncOptions = { title: title, defaultPath: store.get("default_dir", app.getPath("documents")), filters: [ {name: 'json', extensions: ['json']}, {name: 'All Files', extensions: ['*']} ] } let file_path; if (for_save) { file_path = dialog.showSaveDialogSync(options); } else { file_path = dialog.showOpenDialogSync(options); } if (file_path) { if (for_save) { return file_path } else { return file_path[0]; } } } function openFile(file_path="", make_new_file=false, make_new_window=false) { if (file_path === "") { file_path = chooseFile("Open website data"); } if (file_path && fs.existsSync(file_path)) { let result = checkFile(file_path); if (result) { let json = getUpdatedJson(result); updateRecentFiles(file_path); if (make_new_file) { let old_window = BrowserWindow.getFocusedWindow(); createEditorWindow(file_path, json); if (old_window && !make_new_window) { old_window.close(); } } else { let window = BrowserWindow.getFocusedWindow(); window.webContents.send("open", file_path, json); } } } } function saveFile(file_name, json_data) { if (file_name == null) { let file_path = chooseFile("Save website data", true); if (file_path) { updateRecentFiles(file_path); file_name = file_path; } } if (file_name) { fs.writeFileSync(file_name, JSON.stringify(json_data, null, 4)); BrowserWindow.getFocusedWindow().webContents.send("file-saved", file_name); } } function updateRecentFiles(new_file=null) { let recent_files = >store.get("recent_files"); if (new_file != null) { store.set("default_dir", new_file); if (recent_files.includes(new_file)) { recent_files.splice(recent_files.indexOf(new_file), 1); } recent_files.splice(0, 0, new_file); } let files_to_delete = []; $.each(recent_files, (i, file) => { if (!fs.existsSync(file)) { console.log(file); files_to_delete.push(i); } }); for (let index of files_to_delete) { recent_files.splice(index, 1); } store.set("recent_files", recent_files); updateMenuBar(); } function updateMenuBar() { let recent_files = >store.get("recent_files"); for (let menu of menu_template[0].submenu) { if (menu.label === "Open Recent") { menu.submenu = [] for (let file of recent_files) { if (fs.existsSync(file)) { menu.submenu.push({ label: file, click: () => { BrowserWindow.getFocusedWindow().webContents.send("try-open", file); } }); } } } } let menu = Menu.buildFromTemplate(menu_template); Menu.setApplicationMenu(menu); } function loadJson(file_path) { let file = fs.readFileSync(file_path); return JSON.parse(file.toString()); } function checkFile(file_path) { let json = loadJson(file_path); let result; let valid_result = false; for (let schema_path of all_schemas) { let schema = loadJson(schema_path); result = validator.validate(json, schema); if (result.valid) { let split_schema = schema_path.split(/[\\/]/); result = {"schema": split_schema[split_schema.length-1], "result": result}; valid_result = true; break; } } if (valid_result) { return result; } } function getUpdatedJson(validation_result) { let json = validation_result["result"].instance; let schema = validation_result["schema"]; if (schema === "current_schema.json") { return json } return schema_updaters[schema](json); } function update_1_0_0_schema(json) { $.each(json, (i, table_data) => { table_data["id"] = "table"+i.toString(); table_data["tab_name"] = table_data["tab-name"]; delete table_data["tab-name"]; let col_dict = {}; $.each(table_data["col-def"], (j, col_data) => { col_dict[col_data["field"]] = "column"+j.toString(); col_data["field"] = "column"+j.toString(); }); table_data["columns"] = table_data["col-def"]; delete table_data["col-def"]; $.each(table_data["rows"], (j, row_data) => { row_data["uid"] = "row"+j.toString(); $.each(col_dict, (old_key, new_key) => { row_data[new_key] = row_data[old_key]; delete row_data[old_key]; }); }); if (!table_data.hasOwnProperty("filter")) { table_data["filter"] = false; } if (!table_data.hasOwnProperty("hidden")) { table_data["hidden"] = false; } if (!table_data.hasOwnProperty("tab_name")) { table_data["tab_name"] = ""; } if (!table_data.hasOwnProperty("description")) { table_data["description"] = ""; } }); json = { "version": "1.1.0", "tables": json, } return json; } function duplicateJson(json) { return JSON.parse(JSON.stringify(json)); } function removeRecentFile(file) { let recent_files = >store.get("recent_files"); recent_files.splice(recent_files.indexOf(file), 1); store.set("recent_files", recent_files); } ipcMain.handle("get-store-value", (event, key) => { return store.get(key) }) ipcMain.handle("remove-recent-file", (event, file) => { removeRecentFile(file); }) ipcMain.on("create-new", (event) => { createNew(); }) ipcMain.on("save", (event, file_name, json_data) => { saveFile(file_name, json_data); }) ipcMain.on("open", (event, file_path="", new_file=false, new_window=false) => { openFile(file_path, new_file) }) ipcMain.on("quit", (event) => { if (BrowserWindow.getAllWindows().length > 1) { BrowserWindow.getFocusedWindow().destroy(); } else { app.exit(); } }) ipcMain.on("close-window", (event) => { BrowserWindow.getFocusedWindow().destroy(); })