File paths can be given as command line arguments Files can be drop into the splash screen to open
449 lines
11 KiB
TypeScript
449 lines
11 KiB
TypeScript
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: <string>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(<OpenDialogSyncOptions>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 = <Array<string>>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 = <Array<string>>store.get("recent_files");
|
|
for (let menu of <MenuItemConstructorOptions[]>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 = <Array<string>>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();
|
|
})
|