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 += '
'; + 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 += '
'; + h += '' + (collapsed ? '▶' : '▼') + ''; + h += ''; + h += '' + sec.label + ''; + if (!sec.always) { + 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