const { ipcRenderer } = require('electron'); let tables_data; let current_table; let selected_cells = []; let selected_cell_data; let nav = $("nav"); let nav_scroll = nav.scrollLeft(); let detail_tab_name = $("#detail-tab-name"); let detail_table_name = $("#detail-table-name"); let detail_table_hidden = $("#detail-table-hidden"); let detail_column_name = $("#detail-column-name"); let detail_column_selectable = $("#detail-column-selectable"); let detail_column_sortable = $("#detail-column-sortable"); let detail_cell_text = $("#detail-cell-text"); function openFile(file_name) { $.ajax({ dataType: "json", url: file_name, cache: false, success: (json, textStatus, jqXHR) => { tables_data = json; refreshPage(); } }); } function saveFile(create_new_file) { ipcRenderer.send("save", tables_data, create_new_file); } function clearPage() { $("#tables").empty(); nav.empty(); $("#details input, #details textarea").val(""); } function refreshPage() { clearPage(); generateTables(); showTab(tables_data[0]["id"]); } function generateTable(table_data) { const table_id = table_data["id"]; let a = $("") .attr("id", table_id + "-nav") .addClass("nav-link") .attr("href", "#" + table_id) .text(table_data["name"]) .on("click", () => { showTab(table_id); }); let add_table_btn = nav.find("a[id='add-table']"); if (add_table_btn.length !== 0) { add_table_btn.before(a); } else { nav.append(a); } let table_div = $("
") .attr("id", table_id + "-tab") .addClass("tab-pane table-responsive"); let table = $("
") .attr("id", table_id) .addClass("table table-bordered") .append($("") .append($("") .addClass("text-nowrap"))) .append(""); $.each(table_data["col-def"], (i, col) => { const col_id = col["field"]; let header_id = table_id + "-" + col_id; table.find("thead > tr") .append(makeHeader(header_id, col["title"])); }) $.each(table_data["rows"], (i, row) => { const row_id = row["uid"]; let tr = $("") .attr("id", table_id + "-" + row_id); $.each(table_data["col-def"], (j, col) => { const col_id = col["field"]; let cell_id = table_id + "-" + col_id + "-" + row_id; let td = makeCell(cell_id, row[col_id]); tr.append(td); }) table.find("tbody").append(tr); }) table_div.append(table) $("#tables").append(table_div); } function makeCell(cell_id, cell_text) { return $("") .attr("id", cell_id) .text(cell_text) .on("click", () => { selectCell(cell_id); }) .on("contextmenu" , (event) => { cellContextMenu(event, cell_id); }) .on("dblclick", (event) => { cellInlineEdit(event); }); } function makeHeader(header_id, col_title) { return $("") .attr("id", header_id) .text(col_title) .on("click", () => { selectCell(header_id); }) .on("contextmenu", (event) => { headerContextMenu(event, header_id); }) .on("dblclick", (event) => { headerInlineEdit(event); }); } function generateTables() { for (let table_data of tables_data) { generateTable(table_data); } nav.append($("") .attr("id", "add-table") .addClass("nav-link btn-success") .html("") .on("click", () => { addTable(); })) } function updateDetailsPanel() { let data = getSelectedCellData(); detail_tab_name.val(data["tab_name"]); detail_table_name.val(data["table_name"]); detail_column_name.val(data["col_name"]); detail_table_hidden.prop("checked", data["table_hidden"]); detail_column_selectable.prop("checked", data["col_selectable"]); detail_column_sortable.prop("checked", data["col_sortable"]); if (data["row_id"]) { detail_cell_text.attr("disabled", false); detail_cell_text.val(data["cell_text"]); } else { detail_cell_text.attr("disabled", true); detail_cell_text.val(""); } } function showTab(table_name) { current_table = table_name; let tab_name = table_name+"-tab"; $("#tables").children().removeClass("show active"); nav.children().removeClass("active"); $("#"+tab_name).addClass("show active"); $("#"+tab_name.replace("-tab", "-nav")).addClass("active"); selectCell($("table:visible > tbody > tr:first > td:first").attr("id")); } function selectCell(cell_id) { $("td, th").removeClass("selected"); $("#"+cell_id).addClass("selected"); selected_cells = [cell_id]; updateDetailsPanel(); } function getSelectedCellData(force_update=false) { if (!force_update && selected_cell_data && selected_cells[0] === selected_cell_data["selected_id"]) { return selected_cell_data; } let table_pattern = /(table\d+)/; let column_pattern = /(column\d+)/; let row_pattern = /(row\d+)/; let table_match = table_pattern.exec(selected_cells[0]); let column_match = column_pattern.exec(selected_cells[0]); let row_match = row_pattern.exec(selected_cells[0]); let cell_data = { "selected_id": selected_cells[0], "table_id": table_match[0], "table_nav_id": table_match[0]+"-nav", "col_id": table_match[0]+"-"+column_match[0], } $.each(tables_data, (i, table_data) => { if (table_data["id"] === table_match[0]) { cell_data["table_index"] = i; cell_data["table_name"] = table_data["name"]; cell_data["tab_name"] = table_data["tab_name"]; cell_data["table_hidden"] = table_data["hidden"]; $.each(table_data["col-def"], (j, col) => { if (col["field"] === column_match[0]) { cell_data["col_index"] = j; cell_data["col_name"] = col["title"]; cell_data["col_selectable"] = col["selectable"]; cell_data["col_sortable"] = col["sortable"]; return false; } }) if (row_match !== null) { cell_data["row_id"] = table_match[0]+"-"+row_match[0]; cell_data["cell_id"] = table_match[0]+"-"+column_match[0]+"-"+row_match[0]; $.each(table_data["rows"], (j, row) => { if (row["uid"] === row_match[0]) { cell_data["row_index"] = j; cell_data["cell_text"] = row[column_match[0]]; cell_data["row_key"] = column_match[0]; return false; } }) } return false; } }) selected_cell_data = cell_data; return selected_cell_data; } function addTable() { let table_ids = getTableIDs(); let table_num = 0; for (let id of table_ids) { if (table_ids.includes(`table${table_num}`)) { table_num++; } else {break;} } let table_id = `table${table_num}`; let table_data = { "id": table_id, "name": "New Table", "filter": false, "hidden": false, "tab_name": "", "description": "", "col-def": [ { "field": "column0", "title": "Column 1", "sortable": true, "selectable": false } ], "rows": [ { "uid": "row0", "column0": "", } ] } tables_data.push(table_data); generateTable(table_data); } function insertColumn(table_id, index) { if (index < 0){return;} $.each(tables_data, (i, table_data) => { if (table_data["id"] === table_id) { let col_ids = getColumnIDs(table_data["col-def"]); let col_num = 0; for (let id of col_ids) { if (col_ids.includes(`column${col_num}`)) { col_num++; } else {break;} } let col_id = `column${col_num}` table_data["col-def"].splice(index, 0,{ "field": col_id, "title": "New Column", "sortable": false, "selectable": false, }) let num_cols = $(`#${table_id} > thead > tr`).children().length; if (index >= num_cols) { index = num_cols-1; $.fn.insert = function (data) { this.after(data); }; } else { $.fn.insert = function (data) { this.before(data); }; } let header_id = table_id + "-" + col_id; $(`#${table_id} > thead > tr > th:nth-child(${index + 1})`) .insert(makeHeader(header_id, "New Column")); $(`#${table_id} > tbody > tr`).each((j, tr) => { tr = $(tr); let cell_id = table_id+"-"+col_id+"-"+table_data["rows"][j]["uid"]; tr.children(`td:nth-child(${index + 1})`) .insert(makeCell(cell_id, "")) }); getSelectedCellData(true); return false; } }) } function insertRow(table_id, index) { if (index < 0){return;} $.each(tables_data, (i, table_data) => { if (table_data["id"] === table_id) { let row_ids = getRowIDs(table_data["rows"]); let row_num = 0; for (let id of row_ids) { if (row_ids.includes(`row${row_num}`)) { row_num++; } else {break;} } let row_id = `row${row_num}` let col_ids = getColumnIDs(table_data["col-def"]); let row_data = {"uid": row_id}; for (let id of col_ids) { row_data[id] = ""; } table_data["rows"].splice(index, 0, row_data); let num_rows = $(`#${table_id} > tbody > tr`).length; if (index >= num_rows) { index = num_rows-1; $.fn.insert = function (data) { this.after(data); }; } else { $.fn.insert = function (data) { this.before(data); }; } let tr = $("") .attr("id", table_id+"-"+row_id); for (let col_id of col_ids) { let cell_id = table_id+"-"+col_id+"-"+row_id; tr.append(makeCell(cell_id, "")) } $(`#${table_id} > tbody > tr:nth-child(${index+1})`) .insert(tr); getSelectedCellData(true); return false; } }) } function cellContextMenu(event, cell_id) { selectCell(cell_id); let menu = $("
") .attr("id", "context-menu-"+cell_id) .addClass("border context-menu text-nowrap") .on("mouseleave", () => { menu.remove(); }) .append($("") .addClass("btn d-block") .on("click", () => { let data = getSelectedCellData(); insertRow(data["table_id"], data["row_index"]); }) .text("Insert row above")) .append($("") .addClass("btn d-block") .on("click", () => { let data = getSelectedCellData(); insertRow(data["table_id"], data["row_index"]+1); }) .text("Insert row below")) .append($("") .addClass("btn d-block") .on("click", () => { let data = getSelectedCellData(); insertColumn(data["table_id"], data["col_index"]); }) .text("Insert column left")) .append($("") .addClass("btn d-block") .on("click", () => { let data = getSelectedCellData(); insertColumn(data["table_id"], data["col_index"]+1); }) .text("Insert column right")) let max_height = $(document).height(); let max_width = $(document).width(); let x_pos = event.pageX-5; let y_pos = event.pageY-5; $("body").append(menu); y_pos = Math.min(y_pos, max_height-menu.height()); x_pos = Math.min(x_pos, max_width-menu.width()); menu.css({"top": y_pos, "left": x_pos}); } function headerContextMenu(event, header_id) { if (!selected_cells.includes(header_id)) { selectCell(header_id); } let menu = $("
") .attr("id", "context-menu-"+header_id) .addClass("border context-menu text-nowrap") .on("mouseleave", () => { menu.remove(); }) .append($("") .addClass("btn d-block") .on("click", () => { let data = getSelectedCellData(); insertColumn(data["table_id"], data["col_index"]); }) .text("Insert column left")) .append($("") .addClass("btn d-block") .on("click", () => { let data = getSelectedCellData(); insertColumn(data["table_id"], data["col_index"]+1); }) .text("Insert column right")) let max_height = $(document).height(); let max_width = $(document).width(); let x_pos = event.pageX-5; let y_pos = event.pageY-5; $("body").append(menu); y_pos = Math.min(y_pos, max_height-menu.height()); x_pos = Math.min(x_pos, max_width-menu.width()); menu.css({"top": y_pos, "left": x_pos}); } function cellInlineEdit(event) { let data = getSelectedCellData() let target = $(event.target); let text_area = $("