Added schema for data file validation
Can open older versions of data files
This commit is contained in:
parent
20031fb918
commit
4096d83596
@ -48,7 +48,8 @@
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"electron-store": "^6.0.1",
|
||||
"jquery": "^3.5.1",
|
||||
"jsdom": "^16.4.0"
|
||||
"jsdom": "^16.4.0",
|
||||
"jsonschema": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "6.0.0-beta.52",
|
||||
|
@ -1,31 +1,34 @@
|
||||
[
|
||||
{
|
||||
"id": "table0",
|
||||
"name": "Table 1",
|
||||
"filter": false,
|
||||
"hidden": false,
|
||||
"tab_name": "",
|
||||
"description": "",
|
||||
"col-def": [
|
||||
{
|
||||
"field": "column0",
|
||||
"title": "Column 1",
|
||||
"sortable": true,
|
||||
"selectable": false
|
||||
},
|
||||
{
|
||||
"field": "column1",
|
||||
"title": "Column 2",
|
||||
"sortable": true,
|
||||
"selectable": false
|
||||
}
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"uid": "row0",
|
||||
"column0": "cell data",
|
||||
"column1": "cell data"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
{
|
||||
"version": "1.1.0",
|
||||
"tables": [
|
||||
{
|
||||
"id": "table0",
|
||||
"name": "Table 1",
|
||||
"filter": false,
|
||||
"hidden": false,
|
||||
"tab_name": "",
|
||||
"description": "",
|
||||
"columns": [
|
||||
{
|
||||
"field": "column0",
|
||||
"title": "Column 1",
|
||||
"sortable": true,
|
||||
"selectable": false
|
||||
},
|
||||
{
|
||||
"field": "column1",
|
||||
"title": "Column 2",
|
||||
"sortable": true,
|
||||
"selectable": false
|
||||
}
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"uid": "row0",
|
||||
"column0": "cell data",
|
||||
"column1": "cell data"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
164
src/index.js
164
src/index.js
@ -1,11 +1,20 @@
|
||||
const { app, BrowserWindow, ipcMain, dialog, Menu } = require('electron');
|
||||
const Store = require("electron-store");
|
||||
const fs = require("fs");
|
||||
const util = require('util')
|
||||
let Validator = require("jsonschema").Validator;
|
||||
let validator = new Validator();
|
||||
|
||||
var jsdom = require("jsdom");
|
||||
let all_schemas = [
|
||||
"src/schemas/current_schema.json",
|
||||
"src/schemas/1.0.0_schema.json",
|
||||
]
|
||||
let schema_updaters = {
|
||||
"1.0.0_schema.json": update_1_0_0_schema
|
||||
}
|
||||
let jsdom = require("jsdom");
|
||||
const { JSDOM } = jsdom;
|
||||
const { window } = new JSDOM();
|
||||
// const { document } = (new JSDOM('')).window;
|
||||
let $ = jQuery = require('jquery')(window);
|
||||
|
||||
const store = new Store();
|
||||
@ -51,6 +60,58 @@ let menu_template = [
|
||||
}
|
||||
]
|
||||
|
||||
let table_col_schema = {
|
||||
"id": "/table_column",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {"type": "string"},
|
||||
"title": {"type": "string"},
|
||||
"sortable": {"type": "boolean"},
|
||||
"selectable": {"type": "boolean"},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let table_row_schema = {
|
||||
"id": "/table_row",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uid": {"type": "string", "pattern": /row\d+/},
|
||||
},
|
||||
"patternProperties": {
|
||||
"column\d+": {"type": "string"}
|
||||
},
|
||||
"minProperties": 2
|
||||
}
|
||||
}
|
||||
|
||||
let table_schema = {
|
||||
"id": "/table",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string"},
|
||||
"name": {"type": "string"},
|
||||
"filter": {"type": "boolean"},
|
||||
"hidden": {"type": "boolean"},
|
||||
"tab_name": {"type": "string"},
|
||||
"description": {"type": "string"},
|
||||
"columns": {"$ref": "/table_column"},
|
||||
"rows": {"$ref": "/table_row"}
|
||||
}
|
||||
}
|
||||
|
||||
let data_file_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": {"type": "string"},
|
||||
"tables": {"$ref": "/table"}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||
if (require('electron-squirrel-startup')) { // eslint-disable-line global-require
|
||||
app.quit();
|
||||
@ -158,23 +219,27 @@ function openFile(file_path="", new_file=false) {
|
||||
file_path = chooseFile("Open website data");
|
||||
}
|
||||
if (file_path) {
|
||||
updateRecentFiles(file_path);
|
||||
current_file = file_path
|
||||
if (new_file) {
|
||||
let old_window = BrowserWindow.getFocusedWindow();
|
||||
let window = createEditorWindow();
|
||||
window.once("ready-to-show", () => {
|
||||
window.webContents.send("open", file_path);
|
||||
})
|
||||
old_window.close();
|
||||
} else {
|
||||
let window = BrowserWindow.getFocusedWindow();
|
||||
window.webContents.send("open", file_path);
|
||||
let result = checkFile(file_path);
|
||||
if (result) {
|
||||
let json = getUpdatedJson(result);
|
||||
updateRecentFiles(file_path);
|
||||
current_file = file_path
|
||||
if (new_file) {
|
||||
let old_window = BrowserWindow.getFocusedWindow();
|
||||
let window = createEditorWindow();
|
||||
window.once("ready-to-show", () => {
|
||||
window.webContents.send("open", json);
|
||||
})
|
||||
old_window.close();
|
||||
} else {
|
||||
let window = BrowserWindow.getFocusedWindow();
|
||||
window.webContents.send("open", json);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveFile(tables_data, create_new_file) {
|
||||
function saveFile(json_data, create_new_file) {
|
||||
if (current_file == null || create_new_file) {
|
||||
let file_path = chooseFile("Save website data", true);
|
||||
if (file_path) {
|
||||
@ -183,7 +248,8 @@ function saveFile(tables_data, create_new_file) {
|
||||
}
|
||||
}
|
||||
if (current_file) {
|
||||
fs.writeFileSync(current_file, JSON.stringify(tables_data, null, 4));
|
||||
let tables_data = json_data["tables"]
|
||||
fs.writeFileSync(current_file, JSON.stringify(json_data, null, 4));
|
||||
let nav_content_path = current_file.replace(".json", "-nav-content.html");
|
||||
let tab_content_path = current_file.replace(".json", "-tab-content.html");
|
||||
let nav = "";
|
||||
@ -309,6 +375,68 @@ function updateMenuBar() {
|
||||
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;
|
||||
let col_dict = {};
|
||||
$.each(table_data["col-def"], (j, col_data) => {
|
||||
col_dict[col_data["field"]] = "column"+j;
|
||||
col_data["field"] = "column"+j;
|
||||
});
|
||||
table_data["columns"] = table_data["col-def"];
|
||||
delete table_data["col-def"];
|
||||
$.each(table_data["rows"], (j, row_data) => {
|
||||
row_data["uid"] = "row"+j;
|
||||
$.each(col_dict, (old_key, new_key) => {
|
||||
row_data[new_key] = row_data[old_key];
|
||||
delete row_data[old_key];
|
||||
});
|
||||
});
|
||||
});
|
||||
json = {
|
||||
"version": "1.1.0",
|
||||
"tables": json,
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
function duplicateJson(json) {
|
||||
return JSON.parse(JSON.stringify(json));
|
||||
}
|
||||
|
||||
ipcMain.handle("get-store-value", (event, key) => {
|
||||
return store.get(key)
|
||||
})
|
||||
@ -317,8 +445,8 @@ ipcMain.on("create-new", (event) => {
|
||||
createNew();
|
||||
})
|
||||
|
||||
ipcMain.on("save", (event, tables_data, create_new_file) => {
|
||||
saveFile(tables_data, create_new_file);
|
||||
ipcMain.on("save", (event, json_data, create_new_file) => {
|
||||
saveFile(json_data, create_new_file);
|
||||
})
|
||||
|
||||
ipcMain.on("open", (event, file_path="") => {
|
||||
|
@ -1,4 +1,5 @@
|
||||
const { ipcRenderer } = require('electron');
|
||||
let json_data;
|
||||
let tables_data;
|
||||
let current_table;
|
||||
let selected_cells = [];
|
||||
@ -15,21 +16,29 @@ let detail_column_sortable = $("#detail-column-sortable");
|
||||
let detail_cell_text = $("#detail-cell-text");
|
||||
let position_edit_btn = $("#position-edit");
|
||||
|
||||
function openFile(file_name) {
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url: file_name,
|
||||
cache: false,
|
||||
success: (json, textStatus, jqXHR) => {
|
||||
tables_data = json;
|
||||
current_table = null;
|
||||
refreshPage();
|
||||
}
|
||||
});
|
||||
function openFile(file) {
|
||||
if (typeof file == "object") {
|
||||
json_data = file;
|
||||
tables_data = file["tables"];
|
||||
current_table = null;
|
||||
refreshPage();
|
||||
} else {
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url: file,
|
||||
cache: false,
|
||||
success: (json, textStatus, jqXHR) => {
|
||||
json_data = json;
|
||||
tables_data = json["tables"];
|
||||
current_table = null;
|
||||
refreshPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function saveFile(create_new_file) {
|
||||
ipcRenderer.send("save", tables_data, create_new_file);
|
||||
ipcRenderer.send("save", json_data, create_new_file);
|
||||
}
|
||||
|
||||
function clearPage() {
|
||||
@ -81,7 +90,7 @@ function generateTable(table_data, table_index) {
|
||||
.addClass("text-nowrap")))
|
||||
.append("<tbody>");
|
||||
|
||||
$.each(table_data["col-def"], (i, col) => {
|
||||
$.each(table_data["columns"], (i, col) => {
|
||||
const col_id = col["field"];
|
||||
let header_id = table_id + "-" + col_id;
|
||||
table.find("thead > tr")
|
||||
@ -92,7 +101,7 @@ function generateTable(table_data, table_index) {
|
||||
const row_id = row["uid"];
|
||||
let tr = $("<tr></tr>")
|
||||
.attr("id", table_id + "-" + row_id);
|
||||
$.each(table_data["col-def"], (j, col) => {
|
||||
$.each(table_data["columns"], (j, col) => {
|
||||
const col_id = col["field"];
|
||||
let cell_id = table_id + "-" + col_id + "-" + row_id;
|
||||
let td = makeCell(cell_id, row[col_id]);
|
||||
@ -233,8 +242,8 @@ function getSelectedCellData(force_update=false) {
|
||||
cell_data["tab_name"] = table_data["tab_name"];
|
||||
cell_data["table_hidden"] = table_data["hidden"];
|
||||
cell_data["num_rows"] = table_data["rows"].length;
|
||||
cell_data["num_cols"] = table_data["col-def"].length;
|
||||
$.each(table_data["col-def"], (j, col) => {
|
||||
cell_data["num_cols"] = table_data["columns"].length;
|
||||
$.each(table_data["columns"], (j, col) => {
|
||||
if (col["field"] === column_match[0]) {
|
||||
cell_data["col_index"] = j;
|
||||
cell_data["col_name"] = col["title"];
|
||||
@ -278,7 +287,7 @@ function addTable() {
|
||||
"hidden": false,
|
||||
"tab_name": "",
|
||||
"description": "",
|
||||
"col-def": [
|
||||
"columns": [
|
||||
{
|
||||
"field": "column0",
|
||||
"title": "Column 1",
|
||||
@ -321,8 +330,8 @@ function duplicateTable(table_id) {
|
||||
function insertColumn(table_id, index=-1) {
|
||||
$.each(tables_data, (i, table_data) => {
|
||||
if (table_data["id"] === table_id) {
|
||||
if (index === -1) {index = table_data["col-def"].length}
|
||||
let col_ids = getColumnIDs(table_data["col-def"]);
|
||||
if (index === -1) {index = table_data["columns"].length}
|
||||
let col_ids = getColumnIDs(table_data["columns"]);
|
||||
let col_num = 0;
|
||||
for (let id of col_ids) {
|
||||
if (col_ids.includes(`column${col_num}`)) {
|
||||
@ -330,7 +339,7 @@ function insertColumn(table_id, index=-1) {
|
||||
} else {break;}
|
||||
}
|
||||
let col_id = `column${col_num}`
|
||||
table_data["col-def"].splice(index, 0,{
|
||||
table_data["columns"].splice(index, 0,{
|
||||
"field": col_id,
|
||||
"title": "New Column",
|
||||
"sortable": false,
|
||||
@ -376,7 +385,7 @@ function insertRow(table_id, index) {
|
||||
} else {break;}
|
||||
}
|
||||
let row_id = `row${row_num}`
|
||||
let col_ids = getColumnIDs(table_data["col-def"]);
|
||||
let col_ids = getColumnIDs(table_data["columns"]);
|
||||
let row_data = {"uid": row_id};
|
||||
for (let id of col_ids) {
|
||||
row_data[id] = "";
|
||||
@ -423,8 +432,8 @@ function deleteTable(table_id) {
|
||||
function deleteColumn(table_id, col_index) {
|
||||
for (let table_data of tables_data) {
|
||||
if (table_data["id"] === table_id) {
|
||||
let field = table_data["col-def"][col_index]["field"];
|
||||
table_data["col-def"].splice(col_index, 1);
|
||||
let field = table_data["columns"][col_index]["field"];
|
||||
table_data["columns"].splice(col_index, 1);
|
||||
|
||||
for (let row of table_data["rows"]) {
|
||||
delete row[field];
|
||||
@ -665,7 +674,7 @@ function closeHeaderInlineEdit(save=false) {
|
||||
$("th.inline-edit").html(val)
|
||||
.removeClass("inline-edit");
|
||||
let data = getSelectedCellData();
|
||||
tables_data[data["table_index"]]["col-def"][data["col_index"]]["title"] = val;
|
||||
tables_data[data["table_index"]]["columns"][data["col_index"]]["title"] = val;
|
||||
selected_cell_data["col_name"] = val;
|
||||
detail_column_name.val(val);
|
||||
} else {
|
||||
@ -697,9 +706,9 @@ function moveRow(table_id, old_index, new_index) {
|
||||
function moveCol(table_id, old_index, new_index) {
|
||||
for (let table_data of tables_data) {
|
||||
if (table_data["id"] === table_id) {
|
||||
let col = table_data["col-def"][old_index];
|
||||
table_data["col-def"].splice(old_index, 1);
|
||||
table_data["col-def"].splice(new_index, 0, col);
|
||||
let col = table_data["columns"][old_index];
|
||||
table_data["columns"].splice(old_index, 1);
|
||||
table_data["columns"].splice(new_index, 0, col);
|
||||
}
|
||||
}
|
||||
refreshPage();
|
||||
@ -836,7 +845,7 @@ detail_column_name.on("input", (event) => {
|
||||
let new_col_name = input.val();
|
||||
let data = getSelectedCellData();
|
||||
$(`#${data["col_id"]}`).text(new_col_name);
|
||||
tables_data[data["table_index"]]["col-def"][data["col_index"]]["title"] = new_col_name;
|
||||
tables_data[data["table_index"]]["columns"][data["col_index"]]["title"] = new_col_name;
|
||||
selected_cell_data["col_name"] = new_col_name;
|
||||
});
|
||||
|
||||
@ -844,7 +853,7 @@ detail_column_selectable.on("input", (event) => {
|
||||
let input = $(event.target);
|
||||
let column_selectable = input.prop("checked");
|
||||
let data = getSelectedCellData();
|
||||
tables_data[data["table_index"]]["col-def"][data["col_index"]]["selectable"] = column_selectable;
|
||||
tables_data[data["table_index"]]["columns"][data["col_index"]]["selectable"] = column_selectable;
|
||||
selected_cell_data["col_selectable"] = column_selectable;
|
||||
});
|
||||
|
||||
@ -852,7 +861,7 @@ detail_column_sortable.on("input", (event) => {
|
||||
let input = $(event.target);
|
||||
let column_sortable = input.prop("checked");
|
||||
let data = getSelectedCellData();
|
||||
tables_data[data["table_index"]]["col-def"][data["col_index"]]["sortable"] = column_sortable;
|
||||
tables_data[data["table_index"]]["columns"][data["col_index"]]["sortable"] = column_sortable;
|
||||
selected_cell_data["col_sortable"] = column_sortable;
|
||||
});
|
||||
|
||||
@ -873,8 +882,8 @@ ipcRenderer.on("open-default", (event) => {
|
||||
openFile("default.json");
|
||||
});
|
||||
|
||||
ipcRenderer.on("open", (event, file_path) => {
|
||||
openFile(file_path);
|
||||
ipcRenderer.on("open", (event, json) => {
|
||||
openFile(json);
|
||||
})
|
||||
|
||||
ipcRenderer.on("save", (event, create_new_file) => {
|
||||
|
40
src/schemas/1.0.0_schema.json
Normal file
40
src/schemas/1.0.0_schema.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"filter": {"type": "boolean"},
|
||||
"hidden": {"type": "boolean"},
|
||||
"tab_name": {"type": "string"},
|
||||
"description": {"type": "string"},
|
||||
"col-def": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {"type": "string"},
|
||||
"title": {"type": "string"},
|
||||
"sortable": {"type": "boolean"},
|
||||
"selectable": {"type": "boolean"}
|
||||
},
|
||||
"required": ["field", "title"]
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"rows": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uid": {"type": "string"}
|
||||
},
|
||||
"minProperties": 2,
|
||||
"required": ["uid"]
|
||||
},
|
||||
"minItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["name", "col-def", "rows"]
|
||||
}
|
||||
}
|
52
src/schemas/current_schema.json
Normal file
52
src/schemas/current_schema.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": {"type": "string", "enum": ["1.1.0"]},
|
||||
"tables": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string"},
|
||||
"name": {"type": "string"},
|
||||
"filter": {"type": "boolean"},
|
||||
"hidden": {"type": "boolean"},
|
||||
"tab_name": {"type": "string"},
|
||||
"description": {"type": "string"},
|
||||
"columns": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {"type": "string"},
|
||||
"title": {"type": "string"},
|
||||
"sortable": {"type": "boolean"},
|
||||
"selectable": {"type": "boolean"}
|
||||
},
|
||||
"required": ["field", "title"]
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"rows": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uid": {"type": "string", "pattern": "row\\d+"}
|
||||
},
|
||||
"patternProperties": {
|
||||
"column\\d+": {"type": "string"}
|
||||
},
|
||||
"minProperties": 2,
|
||||
"required": ["uid"]
|
||||
},
|
||||
"minItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["id", "name", "columns", "rows"]
|
||||
},
|
||||
"minItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["version", "tables"]
|
||||
}
|
30
test_data/1.0.0.json
Normal file
30
test_data/1.0.0.json
Normal file
@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"name": "Table 1",
|
||||
"filter": false,
|
||||
"hidden": false,
|
||||
"tab_name": "",
|
||||
"description": "",
|
||||
"col-def": [
|
||||
{
|
||||
"field": "column1",
|
||||
"title": "Column 1",
|
||||
"sortable": true,
|
||||
"selectable": false
|
||||
},
|
||||
{
|
||||
"field": "column2",
|
||||
"title": "Column 2",
|
||||
"sortable": true,
|
||||
"selectable": false
|
||||
}
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"uid": "row0",
|
||||
"column1": "cell data",
|
||||
"column2": "cell data"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -1899,6 +1899,11 @@ jsonfile@^6.0.1:
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsonschema@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2"
|
||||
integrity sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw==
|
||||
|
||||
jsprim@^1.2.2:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
|
||||
|
Loading…
Reference in New Issue
Block a user