From 034187d1c434b6bee32aeb539ea543f6ec537376 Mon Sep 17 00:00:00 2001
From: historia <[not public]>
Date: Fri, 3 Jul 2026 16:55:23 -0400
Subject: feat: card ui added to drops, hazards, techs, and mods in admin gui
---
internal/admin/static/moduleeditor.js | 318 ++++++++++++++++++++++++++++++++++
1 file changed, 318 insertions(+)
create mode 100644 internal/admin/static/moduleeditor.js
(limited to 'internal/admin/static/moduleeditor.js')
diff --git a/internal/admin/static/moduleeditor.js b/internal/admin/static/moduleeditor.js
new file mode 100644
index 0000000..128148d
--- /dev/null
+++ b/internal/admin/static/moduleeditor.js
@@ -0,0 +1,318 @@
+var moduleData = null;
+var moduleID = null;
+var expandedCards = {};
+var addedSections = {};
+var cardWidths = {};
+
+var MODULE_SECTIONS = [
+ {
+ id: 'core', label: 'Core', always: true,
+ fields: [
+ ['name', 'Name', 'text'],
+ ['level', 'Level', 'number', '', 'narrow'],
+ ['max_hit', 'Max Hit', 'number', '', 'narrow'],
+ ['base_xp', 'Base XP', 'number', '', 'narrow'],
+ ['category', 'Category', 'select', 'combat|utility|enchant|processing|transport'],
+ ['element', 'Element', 'text'],
+ ['destination', 'Destination', 'number', '', 'narrow'],
+ ],
+ inline: [
+ {label:'Junk Cost', key:'junk_cost', type:'kv', valType:'number'},
+ ]
+ },
+ {
+ id: 'sequence', label: 'Sequence', path: 'sequence', isArray: true,
+ detect: function(d) { return d.sequence && Array.isArray(d.sequence); },
+ fields: [
+ ['message', 'Message', 'text', '', 'wide'],
+ ['delay', 'Delay', 'number', '', 'narrow'],
+ ]
+ }
+];
+
+function initModuleEditor() {
+ editorType = 'modules';
+ editorFields = [];
+ loadList();
+ if (window.location.hash) loadModuleEditor(window.location.hash.substring(1));
+}
+
+function cardPath(sec) { return sec.path || ''; }
+
+function getVal(data, sec, key) {
+ return ced_getVal(data, sec, key);
+}
+
+function fieldID(sec, key, idx) {
+ return ced_fieldID(sec, key, idx);
+}
+
+function setupSearchFields() { ced_setupSearchFields(); }
+
+function collectFieldValue(el, type) { return ced_collectFieldValue(el, type); }
+
+function loadModuleEditor(id) {
+ moduleID = id;
+ currentID = id;
+ renderList('');
+ window.location.hash = id;
+ API.get('/api/modules/' + encodeURIComponent(id)).then(function(data) {
+ moduleData = data;
+ renderModuleEditor(id, data);
+ }).catch(function(e) {
+ $('#editorMain').innerHTML = '
Failed to load: ' + esc(e.message) + '
';
+ });
+}
+
+function loadItem(id) { loadModuleEditor(id); }
+
+function renderModuleEditor(id, data) {
+ var html = 'Module: ' + esc(id) + '
';
+ html += '';
+ html += '
';
+
+ html += '';
+ html += '
';
+ MODULE_SECTIONS.forEach(function(sec) {
+ if (!sec.always && sec.detect && !sec.detect(data) && !addedSections[sec.id]) return;
+ html += renderCard(sec, data);
+ });
+ html += '
';
+
+ html += '
';
+ html += '';
+ html += '';
+ html += '
';
+ html += '
';
+
+ html += '';
+ html += '
' + esc(data._raw || JSON.stringify(data, null, 2)) + '
';
+ html += '
';
+
+ html += '';
+ html += '';
+ html += '';
+ html += '
';
+
+ $('#editorMain').innerHTML = html;
+ setupSearchFields();
+}
+
+function renderCurrent() {
+ if (!moduleData || !moduleID) return;
+ renderModuleEditor(moduleID, moduleData);
+}
+
+function renderCard(sec, data) {
+ var collapsed = expandedCards[sec.id] === false;
+ var isFull = cardWidths[sec.id] !== undefined ? cardWidths[sec.id] : (sec.fullWidth || false);
+ var fullClass = isFull ? ' obj-card-full' : '';
+ var h = '';
+ h += '';
+ if (!collapsed) {
+ h += '
';
+
+ if (sec.isArray) {
+ h += renderArraySection(sec, data);
+ } else {
+ h += '
';
+ sec.fields.forEach(function(f) {
+ h += renderField(sec, f, data, -1);
+ });
+ h += '
';
+
+ if (sec.inline) {
+ sec.inline.forEach(function(il) {
+ if (il.type === 'kv') {
+ h += ced_renderKV(sec, il, data);
+ }
+ });
+ }
+ }
+ h += '
';
+ }
+ h += '
';
+ return h;
+}
+
+function renderArraySection(sec, data) {
+ var arr = moduleData[cardPath(sec)] || [];
+ var h = '';
+ arr.forEach(function(item, idx) {
+ h += '';
+ h += '
';
+ sec.fields.forEach(function(f) {
+ h += renderField(sec, f, data, idx);
+ });
+ h += '
';
+ h += '
';
+ h += '
';
+ });
+ h += '';
+ return h;
+}
+
+function renderField(sec, f, data, idx) {
+ return ced_renderField(sec, f, data, idx, null, null, cardPath, fieldID, getVal);
+}
+
+function toggleCard(id) {
+ expandedCards[id] = expandedCards[id] === false ? true : false;
+ renderCurrent();
+}
+
+function toggleCardWidth(id) {
+ cardWidths[id] = !cardWidths[id];
+ renderCurrent();
+}
+
+function removeSection(id) {
+ var sec = MODULE_SECTIONS.find(function(s) { return s.id === id; });
+ if (!sec) return;
+ if (sec.path) {
+ delete moduleData[sec.path];
+ }
+ delete addedSections[id];
+ renderCurrent();
+}
+
+function addSection() {
+ var sel = $('#addSectionSelect');
+ var id = sel.value;
+ if (!id) return;
+ var sec = MODULE_SECTIONS.find(function(s) { return s.id === id; });
+ if (!sec) return;
+
+ if (sec.path && sec.isArray) {
+ moduleData[sec.path] = [];
+ } else if (sec.path) {
+ moduleData[sec.path] = {};
+ }
+ addedSections[id] = true;
+ expandedCards[id] = true;
+ renderCurrent();
+}
+
+function addArrayItem(cardID) {
+ var sec = MODULE_SECTIONS.find(function(s) { return s.id === cardID; });
+ if (!sec || !sec.isArray) return;
+ var arr = moduleData[sec.path] || [];
+ var empty = {};
+ sec.fields.forEach(function(f) {
+ empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : '');
+ });
+ arr.push(empty);
+ moduleData[sec.path] = arr;
+ renderCurrent();
+}
+
+function removeArrayItem(cardID, idx) {
+ var sec = MODULE_SECTIONS.find(function(s) { return s.id === cardID; });
+ if (!sec || !sec.isArray) return;
+ var arr = moduleData[sec.path] || [];
+ arr.splice(idx, 1);
+ renderCurrent();
+}
+
+function saveModule() {
+ if (!ced_validateFields(MODULE_SECTIONS, fieldID, null)) return;
+
+ var out = {};
+ MODULE_SECTIONS.forEach(function(sec) {
+ if (sec.isArray) {
+ var arr = [];
+ var card = document.querySelector('.obj-card[data-card="' + sec.id + '"]');
+ if (!card) return;
+ var rows = card.querySelectorAll('.obj-sub-row');
+ rows.forEach(function(row, idx) {
+ var item = {};
+ sec.fields.forEach(function(f) {
+ var fid = fieldID(sec, f[0], idx);
+ var el = document.getElementById(fid);
+ if (el) item[f[0]] = collectFieldValue(el, f[2]);
+ });
+ if (Object.keys(item).length > 0) arr.push(item);
+ });
+ if (arr.length > 0) { out[sec.path] = arr; }
+ return;
+ }
+
+ var sectionObj = {};
+ var hasValues = sec.always;
+
+ if (sec.fields) {
+ sec.fields.forEach(function(f) {
+ var fid = fieldID(sec, f[0]);
+ var el = document.getElementById(fid);
+ if (!el) return;
+ var val = collectFieldValue(el, f[2]);
+ if (val !== '' && val !== false && val !== 0 && val !== null) hasValues = true;
+ sectionObj[f[0]] = val;
+ });
+ }
+
+ if (sec.inline) {
+ sec.inline.forEach(function(il) {
+ if (il.type === 'kv') {
+ var kvEl = document.querySelector('.obj-kv-list[data-sec="' + sec.id + '"][data-key="' + il.key + '"]');
+ if (kvEl) {
+ var map = {};
+ kvEl.querySelectorAll('.obj-kv-row').forEach(function(row) {
+ var keyEl = row.querySelector('.obj-kv-key');
+ var valEl = row.querySelector('.obj-kv-val');
+ if (keyEl && keyEl.value.trim() && valEl) {
+ map[keyEl.value.trim()] = parseFloat(valEl.value) || 0;
+ }
+ });
+ if (Object.keys(map).length > 0) { sectionObj[il.key] = map; hasValues = true; }
+ }
+ }
+ });
+ }
+
+ if (sec.always) {
+ Object.keys(sectionObj).forEach(function(k) { out[k] = sectionObj[k]; });
+ } else if (hasValues && sec.path) {
+ out[sec.path] = sectionObj;
+ } else if (hasValues) {
+ Object.keys(sectionObj).forEach(function(k) { out[k] = sectionObj[k]; });
+ }
+ });
+
+ ced_cleanOutput(out);
+ out.id = moduleID;
+
+ API.put('/api/modules/' + encodeURIComponent(moduleID), out).then(function(resp) {
+ notify('Module ' + moduleID + ' saved', 'success');
+ updateUndoBar();
+ if (resp && resp._raw) moduleData._raw = resp._raw;
+ }).catch(function(e) { notify('Save failed: ' + e.message, 'error'); });
+}
+
+function deleteModule() {
+ if (!confirm('Delete module "' + moduleID + '"?')) return;
+ API.del('/api/modules/' + encodeURIComponent(moduleID)).then(function() {
+ notify('Module ' + moduleID + ' deleted', 'success');
+ updateUndoBar();
+ moduleID = null;
+ currentID = null;
+ moduleData = null;
+ $('#editorMain').innerHTML = 'Deleted
';
+ loadList();
+ }).catch(function(e) { notify('Delete failed: ' + e.message, 'error'); });
+}
--
cgit v1.2.3