363 lines
11 KiB
JavaScript
363 lines
11 KiB
JavaScript
import {deserializeTable, serializeTable, calculateColumnLength} from "./tables.mjs";
|
|
|
|
let loadDialog = document.querySelector("#load-dialog");
|
|
|
|
let itemContextMenu = document.querySelector("#item-context-menu");
|
|
let deleteRow = itemContextMenu.querySelector("#delete-row");
|
|
let deleteColumn = itemContextMenu.querySelector("#delete-column");
|
|
let insertRowAbove = itemContextMenu.querySelector("#insert-row-above");
|
|
let insertRowBelow = itemContextMenu.querySelector("#insert-row-below");
|
|
let insertColumnLeft = itemContextMenu.querySelector("#insert-column-left");
|
|
let insertColumnRight = itemContextMenu.querySelector("#insert-column-right");
|
|
|
|
let output = document.querySelector("#output");
|
|
let tableElement = document.querySelector("table");
|
|
let table = {
|
|
columns: [
|
|
{text: "ABC", leftAligned: false, rightAligned: false},
|
|
{text: "DEF", leftAligned: false, rightAligned: false},
|
|
{text: "GHI", leftAligned: false, rightAligned: false}
|
|
],
|
|
rows: [
|
|
["1", "2", "3"],
|
|
["4", "5", "6"],
|
|
["7", "8", "9"]
|
|
]
|
|
};
|
|
|
|
|
|
|
|
Array.prototype.insert = function(index, item) {
|
|
this.splice(index, 0, item);
|
|
};
|
|
|
|
Array.prototype.remove = function(index) {
|
|
this.splice(index, 1);
|
|
};
|
|
|
|
Node.prototype.insertAfter = function(newNode, referenceNode) {
|
|
this.insertBefore(newNode, referenceNode?.nextSibling);
|
|
};
|
|
|
|
NodeList.prototype.indexOf = function(searchElement) {
|
|
for (let [index, element] of this.entries()) {
|
|
if (element === searchElement) {
|
|
return index;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
function findTableIndices(textarea) {
|
|
let index = tableElement.querySelectorAll("textarea").indexOf(textarea);
|
|
let columnIndex = index % table.columns.length;
|
|
let rowIndex = -1;
|
|
|
|
if (index >= table.columns.length) {
|
|
rowIndex = Math.floor(index / table.columns.length - 1);
|
|
}
|
|
|
|
return {columnIndex, rowIndex};
|
|
}
|
|
|
|
function updateItemAlignment(textarea, column) {
|
|
textarea.classList.remove("left");
|
|
textarea.classList.remove("right");
|
|
if (column.leftAligned) {
|
|
textarea.classList.add("left");
|
|
}
|
|
if (column.rightAligned) {
|
|
textarea.classList.add("right");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function createItemElement(item, column) {
|
|
let textarea = document.createElement("textarea");
|
|
textarea.rows = 1;
|
|
textarea.cols = column.maxLength;
|
|
textarea.value = item;
|
|
if (column.leftAligned) {
|
|
textarea.classList.add("left");
|
|
}
|
|
if (column.rightAligned) {
|
|
textarea.classList.add("right");
|
|
}
|
|
return textarea;
|
|
}
|
|
|
|
function createRowElement(row, isHeader) {
|
|
let tr = document.createElement(isHeader ? "th" : "tr");
|
|
|
|
for (let i = 0; i < row.length; i++) {
|
|
let td = document.createElement("td");
|
|
td.appendChild(createItemElement(row[i], table.columns[i]));
|
|
tr.appendChild(td);
|
|
}
|
|
|
|
return tr;
|
|
}
|
|
|
|
function reloadTable() {
|
|
for (let i = 0; i < table.columns.length; i++) {
|
|
table.columns[i].maxLength = calculateColumnLength(table.columns[i], table.rows, i);
|
|
}
|
|
|
|
while (tableElement.lastChild) {
|
|
tableElement.removeChild(tableElement.lastChild);
|
|
}
|
|
|
|
let columnRow = [];
|
|
for (let column of table.columns) {
|
|
columnRow.push(column.text);
|
|
}
|
|
tableElement.appendChild(createRowElement(columnRow, true));
|
|
|
|
for (let row of table.rows) {
|
|
tableElement.appendChild(createRowElement(row, false));
|
|
}
|
|
|
|
output.value = serializeTable(table);
|
|
}
|
|
|
|
|
|
|
|
loadDialog.querySelector("#load-button").addEventListener("click", function() {
|
|
try {
|
|
table = deserializeTable(loadDialog.querySelector("textarea").value);
|
|
reloadTable();
|
|
loadDialog.close();
|
|
} catch (e) {
|
|
loadDialog.querySelector("#error").innerText = e.message;
|
|
}
|
|
}, {passive: true});
|
|
|
|
document.querySelector("#open-load-button").addEventListener("click", function() {
|
|
loadDialog.querySelector("textarea").value = "";
|
|
loadDialog.querySelector("#error").innerText = "";
|
|
loadDialog.showModal();
|
|
}, {passive: true});
|
|
|
|
|
|
|
|
itemContextMenu.querySelector("form").addEventListener("change", function(event) {
|
|
let leftAligned = ["left", "center"].includes(event.target.id);
|
|
let rightAligned = ["center", "right"].includes(event.target.id);
|
|
let columnIndex = parseInt(itemContextMenu.dataset.columnIndex, 10);
|
|
let column = table.columns[columnIndex];
|
|
|
|
column.leftAligned = leftAligned;
|
|
column.rightAligned = rightAligned;
|
|
|
|
updateItemAlignment(tableElement.querySelectorAll("th textarea")[columnIndex], column);
|
|
for (let tr of tableElement.querySelectorAll("tr")) {
|
|
updateItemAlignment(tr.querySelectorAll("textarea")[columnIndex], column);
|
|
}
|
|
|
|
output.value = serializeTable(table);
|
|
});
|
|
|
|
deleteRow.addEventListener("click", function() {
|
|
let rowIndex = parseInt(itemContextMenu.dataset.rowIndex, 10);
|
|
|
|
table.rows.remove(rowIndex);
|
|
tableElement.querySelectorAll("tr")[rowIndex].remove();
|
|
|
|
output.value = serializeTable(table);
|
|
itemContextMenu.close();
|
|
}, {passive: true});
|
|
|
|
deleteColumn.addEventListener("click", function() {
|
|
let columnIndex = parseInt(itemContextMenu.dataset.columnIndex, 10);
|
|
|
|
table.columns.remove(columnIndex);
|
|
for (let row of table.rows) {
|
|
row.remove(columnIndex);
|
|
}
|
|
|
|
for (let tr of tableElement.querySelectorAll("th, tr")) {
|
|
tr.querySelectorAll("td")[columnIndex].remove();
|
|
}
|
|
|
|
output.value = serializeTable(table);
|
|
itemContextMenu.close();
|
|
}, {passive: true});
|
|
|
|
insertRowAbove.addEventListener("click", function() {
|
|
let rowIndex = parseInt(itemContextMenu.dataset.rowIndex, 10);
|
|
|
|
let newRow = new Array(table.columns.length);
|
|
newRow.fill("");
|
|
table.rows.insert(rowIndex, newRow);
|
|
|
|
tableElement.insertBefore(createRowElement(newRow, false), tableElement.querySelectorAll("tr")[rowIndex]);
|
|
|
|
output.value = serializeTable(table);
|
|
itemContextMenu.close();
|
|
}, {passive: true});
|
|
|
|
insertRowBelow.addEventListener("click", function() {
|
|
let rowIndex = parseInt(itemContextMenu.dataset.rowIndex, 10);
|
|
|
|
let newRow = new Array(table.columns.length);
|
|
newRow.fill("");
|
|
table.rows.insert(rowIndex + 1, newRow);
|
|
|
|
let rowElement = createRowElement(newRow, false);
|
|
let referenceNode = rowIndex >= 0
|
|
? tableElement.querySelectorAll("tr")[rowIndex]
|
|
: tableElement.querySelector("th");
|
|
tableElement.insertAfter(rowElement, referenceNode);
|
|
|
|
output.value = serializeTable(table);
|
|
itemContextMenu.close();
|
|
}, {passive: true});
|
|
|
|
insertColumnLeft.addEventListener("click", function() {
|
|
let columnIndex = parseInt(itemContextMenu.dataset.columnIndex, 10);
|
|
|
|
let column = {
|
|
text: "",
|
|
leftAligned: false,
|
|
rightAligned: false,
|
|
maxLength: 3
|
|
};
|
|
table.columns.insert(columnIndex, column);
|
|
for (let row of table.rows) {
|
|
row.insert(columnIndex, "");
|
|
}
|
|
|
|
let td = document.createElement("td");
|
|
td.appendChild(createItemElement("", column));
|
|
|
|
let th = tableElement.querySelector("th");
|
|
for (let tr of tableElement.querySelectorAll("th, tr")) {
|
|
tr.insertBefore(td.cloneNode(true), tr.querySelectorAll("td")[columnIndex]);
|
|
}
|
|
|
|
output.value = serializeTable(table);
|
|
itemContextMenu.close();
|
|
}, {passive: true});
|
|
|
|
insertColumnRight.addEventListener("click", function() {
|
|
let columnIndex = parseInt(itemContextMenu.dataset.columnIndex, 10);
|
|
|
|
let column = {
|
|
text: "",
|
|
leftAligned: false,
|
|
rightAligned: false,
|
|
maxLength: 3
|
|
};
|
|
table.columns.insert(columnIndex + 1, column);
|
|
for (let row of table.rows) {
|
|
row.insert(columnIndex + 1, "");
|
|
}
|
|
|
|
let td = document.createElement("td");
|
|
td.appendChild(createItemElement("", column));
|
|
|
|
let th = tableElement.querySelector("th");
|
|
for (let tr of tableElement.querySelectorAll("th, tr")) {
|
|
tr.insertAfter(td.cloneNode(true), tr.querySelectorAll("td")[columnIndex]);
|
|
}
|
|
|
|
output.value = serializeTable(table);
|
|
itemContextMenu.close();
|
|
}, {passive: true});
|
|
|
|
|
|
|
|
tableElement.addEventListener("input", function(event) {
|
|
let {columnIndex, rowIndex} = findTableIndices(event.target);
|
|
if (rowIndex < 0) {
|
|
// is modifying a header
|
|
table.columns[columnIndex].text = event.target.value;
|
|
} else {
|
|
// is modifying a row
|
|
table.rows[rowIndex][columnIndex] = event.target.value;
|
|
}
|
|
|
|
output.value = serializeTable(table);
|
|
|
|
table.columns[columnIndex].maxLength = calculateColumnLength(table.columns[columnIndex], table.rows, columnIndex);
|
|
for (let rowElement of tableElement.querySelectorAll("th, tr")) {
|
|
rowElement.children[columnIndex].querySelector("textarea").cols = table.columns[columnIndex].maxLength;
|
|
}
|
|
});
|
|
|
|
tableElement.addEventListener("contextmenu", function(event) {
|
|
if (event.shiftKey || event.target.localName !== "textarea") {
|
|
return;
|
|
}
|
|
|
|
let {columnIndex, rowIndex} = findTableIndices(event.target);
|
|
itemContextMenu.dataset.columnIndex = columnIndex;
|
|
itemContextMenu.dataset.rowIndex = rowIndex;
|
|
itemContextMenu.style.margin = 0;
|
|
itemContextMenu.style.top = itemContextMenu.style.left = "0px";
|
|
itemContextMenu.showModal();
|
|
|
|
let rect = itemContextMenu.getBoundingClientRect();
|
|
let top = event.clientY;
|
|
let left = event.clientX;
|
|
|
|
if (top + rect.height > window.innerHeight) {
|
|
top = window.innerHeight - rect.height;
|
|
}
|
|
if (left + rect.width > window.innerWidth) {
|
|
left = window.innerWidth - rect.width;
|
|
}
|
|
itemContextMenu.style.margin = 0;
|
|
itemContextMenu.style.top = `${top}px`;
|
|
itemContextMenu.style.left = `${left}px`;
|
|
|
|
let column = table.columns[columnIndex];
|
|
if (column.leftAligned && column.rightAligned) {
|
|
itemContextMenu.querySelector("#center").checked = true;
|
|
} else if (column.leftAligned) {
|
|
itemContextMenu.querySelector("#left").checked = true;
|
|
} else if (column.rightAligned) {
|
|
itemContextMenu.querySelector("#right").checked = true;
|
|
} else {
|
|
itemContextMenu.querySelector("#none").checked = true;
|
|
}
|
|
|
|
deleteRow.disabled = event.target.closest("th") !== null;
|
|
deleteColumn.disabled = table.columns.length < 2;
|
|
insertRowAbove.disabled = event.target.closest("th") !== null;
|
|
|
|
event.preventDefault();
|
|
});
|
|
|
|
|
|
|
|
document.querySelector("#copy-to-clipboard").addEventListener("click", function() {
|
|
output.select();
|
|
navigator.clipboard.writeText(output.value).then(
|
|
function() {
|
|
// success
|
|
},
|
|
function(reason) {
|
|
// TODO: make visible to user
|
|
console.error(`Failed to copy to clipboard: ${reason}`);
|
|
}
|
|
);
|
|
}, {passive: true});
|
|
|
|
|
|
|
|
window.addEventListener("click", function(event) {
|
|
if (event.target.localName !== "dialog") {
|
|
return;
|
|
}
|
|
|
|
let rect = event.target.getBoundingClientRect();
|
|
if (rect.top <= event.clientY && rect.left <= event.clientX && rect.bottom >= event.clientY && rect.right >= event.clientX) {
|
|
return;
|
|
}
|
|
event.target.close();
|
|
}, {passive: true});
|
|
|
|
reloadTable();
|