WebEditor/src/index.ts
Matthew Welch 5037bf41be Can open multiple files at once now
File paths can be given as command line arguments
Files can be drop into the splash screen to open
2021-01-27 19:10:09 -08:00

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();
})