Added schema for data file validation

Can open older versions of data files
This commit is contained in:
Matthew Welch 2021-01-11 20:14:09 -08:00
parent 20031fb918
commit 4096d83596
8 changed files with 350 additions and 82 deletions

View File

@ -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",

View File

@ -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"
}
]
}
]
}

View File

@ -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="") => {

View File

@ -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) => {

View 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"]
}
}

View 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
View 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"
}
]
}
]

View File

@ -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"