summaryrefslogtreecommitdiff
path: root/card-editor.html
diff options
context:
space:
mode:
authorJakob L. Kreuze <zerodaysfordays@sdf.org>2025-11-26 15:29:13 -0500
committerJakob L. Kreuze <zerodaysfordays@sdf.org>2025-11-26 15:29:13 -0500
commit5dd9489868d24140d4006cc80edf3a3e16de8290 (patch)
tree3ef03b90cc3470195bb6accf9ffee4923040d721 /card-editor.html
parentc6aac96f1c5eb0ed1ee7a21a1e6f43f221737d58 (diff)
card editor: first releasable version
Diffstat (limited to 'card-editor.html')
-rw-r--r--card-editor.html226
1 files changed, 14 insertions, 212 deletions
diff --git a/card-editor.html b/card-editor.html
index b838b59..d4fca8a 100644
--- a/card-editor.html
+++ b/card-editor.html
@@ -1,215 +1,17 @@
<!DOCTYPE html>
<html lang="en">
-<head>
-<meta charset="UTF-8">
-<title>JSON List Editor</title>
-<style>
- body{font-family:Arial,Helvetica,sans-serif;margin:20px;}
- h1{margin-bottom:5px;}
- textarea{width:100%;height:120px;font-family:monospace;}
- button{margin:5px 0;}
- table{border-collapse:collapse;width:100%;margin-top:10px;}
- th,td{border:1px solid #aaa;padding:4px 6px;text-align:left;}
- th{background:#f0f0f0;}
- input[type=text]{width:100%;box-sizing:border-box;}
- .hidden{display:none;}
- .del-btn{background:#e74c3c;color:white;border:none;padding:4px 6px;cursor:pointer;}
-</style>
-</head>
-<body>
-
-<h1>JSON List Editor</h1>
-
-<p>Paste or load a JSON array of objects below and click <strong>Load</strong>.</p>
-
-<textarea id="jsonInput" placeholder='[{"id":1,"name":"Alice","age":30},{"id":2,"name":"Bob","_secret":"xyz"}]'></textarea><br>
-<button id="loadBtn">Load</button>
-<button id="addBtn">New card</button>
-<button id="saveBtn">Download JSON</button>
-<input type="file" id="fileInput" accept=".json" style="display:none;">
-
-<div id="tableContainer"></div>
-
-<script>
-/* --------------- Global State --------------- */
-let data = []; // array of objects
-let fieldKeys = []; // columns that will be shown (no leading "_")
-
-/* --------------- Load & Parse JSON --------------- */
-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})); // clone to avoid accidental mutations
- computeFieldKeys();
- renderTable();
- } catch (e) {
- alert('Error parsing JSON: ' + e.message);
- }
-}
-
-function computeFieldKeys() {
- const keys = new Set();
- data.forEach(obj => {
- Object.keys(obj).forEach(k => {
- if (!k.startsWith('_')) keys.add(k);
- });
- });
- fieldKeys = Array.from(keys);
-}
-
-/* --------------- Render Table --------------- */
-function renderTable() {
- const container = document.getElementById('tableContainer');
- container.innerHTML = ''; // clear previous
-
- if (!data.length) return;
-
- const table = document.createElement('table');
- const thead = document.createElement('thead');
- const headerRow = document.createElement('tr');
-
- // Build header
- fieldKeys.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) => {
- const tr = document.createElement('tr');
- fieldKeys.forEach(k => {
- const td = document.createElement('td');
- const input = document.createElement('textarea');
- input.rows = 2;
- // const input = document.createElement('input');
- // input.type = 'text';
- 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 = 'del-btn';
- delBtn.dataset.row = rowIndex;
- delBtn.addEventListener('click', () => onDeleteRow(rowIndex));
- delTd.appendChild(delBtn);
- tr.appendChild(delTd);
- });
-
- table.appendChild(tbody);
- 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;
-}
-
-/**
- * Remove all keys from an object whose value is the empty string.
- *
- * @param {Object} obj - The source object to clean.
- * @param {boolean} [inPlace=false] - If true the original object is mutated.
- * @returns {Object} A new object (or the original if inPlace is true) without empty‑string values.
- *
- * @example
- * const data = { a: 'foo', b: '', c: 'bar', d: '' };
- * const cleaned = removeEmptyStrings(data);
- * // cleaned === { a: 'foo', c: 'bar' }
- */
-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;
-}
-
-function onDeleteRow(idx) {
- data.splice(idx, 1);
- renderTable();
-}
-
-/* --------------- File I/O --------------- */
-document.getElementById('loadBtn').addEventListener('click', () => {
- const text = document.getElementById('jsonInput').value;
- loadJSON(text);
-});
-
-document.getElementById('saveBtn').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);
-});
-
-/* --------------- Optional: Drag‑and‑Drop or File Input --------------- */
-document.getElementById('fileInput').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);
-});
-
-/* --------------- Optional: Load file via button --------------- */
-const fileBtn = document.createElement('button');
-fileBtn.textContent = 'Load from File';
-fileBtn.addEventListener('click', () => document.getElementById('fileInput').click());
-document.body.insertBefore(fileBtn, document.getElementById('tableContainer'));
-
-/* --------------- Add New Object --------------- */
-document.getElementById('addBtn').addEventListener('click', () => {
- if (!fieldKeys.length) {
- alert('Load a JSON file first, then you can add a new object.');
- return;
- }
- const newObj = {};
- fieldKeys.forEach(k => newObj[k] = '');
- newObj.id = crypto.randomUUID();
- data.push(newObj);
- renderTable();
-});
-</script>
-</body>
+ <head>
+ <meta charset="UTF-8">
+ <title>SRS Anywhere Card Editor</title>
+ <link rel="stylesheet" href="style.css">
+ </head>
+ <body>
+ <div class="status-section">
+ <button id="save-button">Download JSON</button>
+ <input type="file" id="cards-input" accept=".json" />
+ </div>
+ <h1>SRS Anywhere Card Editor</h1>
+ <div id="table-container"></div>
+ <script src="card-editor.js"></script>
+ </body>
</html>