/* Copyright © 2025 Jakob L. Kreuze This file is part of SRS Anywhere. SRS Anywhere is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SRS Anywhere is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with SRS Anywhere. If not, see . */ let data = []; let fieldKeys = {}; function loadJSON(text) { try { const parsed = JSON.parse(text); if (!Array.isArray(parsed)) throw new Error('JSON must be an array of objects'); data = parsed.map(obj => ({...obj})); computeFieldKeys(); renderTable(); } catch (e) { alert('Error parsing JSON: ' + e.message); } } function partition(arr, key = 'type') { return arr.reduce((acc, item) => { const k = item[key] || 'default'; const prop = String(k); if (!acc[prop]) acc[prop] = []; acc[prop].push(item); return acc; }, {}); } function computeFieldKeys() { const partitioned = partition(data); Object.entries(partitioned).forEach(([type, cards]) => { const keys = new Set(); cards.forEach(card => { Object.keys(card).forEach(k => { if (!k.startsWith('_')) keys.add(k); }); }) fieldKeys[type] = Array.from(keys); }); } function renderTable() { const container = document.getElementById('table-container'); container.innerHTML = ''; // clear previous if (!data.length) return; Object.entries(fieldKeys).forEach(([type, keys]) => { const table = document.createElement('table'); const thead = document.createElement('thead'); const headerRow = document.createElement('tr'); const addButton = document.createElement('button'); table.style = "margin-bottom: 32px;" // Build header keys.forEach(k => { const th = document.createElement('th'); th.textContent = k; headerRow.appendChild(th); }); thead.appendChild(headerRow); table.appendChild(thead); // Extra header for Delete button const delTh = document.createElement('th'); delTh.textContent = 'Delete'; headerRow.appendChild(delTh); // Build body const tbody = document.createElement('tbody'); data.forEach((obj, rowIndex) => { if ((obj.type || 'default') != type) { return; } const tr = document.createElement('tr'); keys.forEach(k => { const td = document.createElement('td'); const input = document.createElement('textarea'); input.rows = 2; input.value = obj[k] !== undefined ? obj[k] : ''; input.dataset.row = rowIndex; input.dataset.key = k; input.addEventListener('input', onFieldChange); td.appendChild(input); tr.appendChild(td); }); tbody.appendChild(tr); // Delete button column const delTd = document.createElement('td'); const delBtn = document.createElement('button'); delBtn.textContent = 'x'; delBtn.className = 'delete'; delBtn.dataset.row = rowIndex; delBtn.addEventListener('click', () => { data.splice(idx, 1); renderTable(); }); delTd.appendChild(delBtn); tr.appendChild(delTd); }); table.appendChild(tbody); addButton.innerHTML = '+'; addButton.addEventListener('click', () => { const newObj = {}; keys.forEach(k => newObj[k] = ''); newObj.id = crypto.randomUUID(); data.push(newObj); renderTable(); }); container.appendChild(addButton); container.appendChild(table); }); } function onFieldChange(e) { const row = e.target.dataset.row; const key = e.target.dataset.key; const value = e.target.value; data[row][key] = value; } function removeEmptyStrings(obj, inPlace = false) { if (typeof obj !== 'object' || obj === null) { throw new TypeError('Expected an object'); } // If we’re mutating, work directly on the original const target = inPlace ? obj : {}; for (const key in obj) { if (!Object.prototype.hasOwnProperty.call(obj, key)) continue; const value = obj[key]; if (value === '') { if (!inPlace) continue; // skip adding it to the new object delete target[key]; // remove from the original } else { target[key] = value; // copy non‑empty value } } return target; } window.addEventListener('load', function() { document.getElementById('cards-input').addEventListener('change', evt => { const file = evt.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = e => loadJSON(e.target.result); reader.readAsText(file); }); document.getElementById('save-button').addEventListener('click', () => { if (!data.length) return; data.forEach(removeEmptyStrings); const blob = new Blob([JSON.stringify(data, null, 2)], {type: 'application/json'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'edited.json'; a.click(); URL.revokeObjectURL(url); }); })