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', 'Fire Blast'], ['level', 'Level', 'number', '20', 'narrow'], ['max_hit', 'Max Hit', 'number', '10', 'narrow'], ['base_xp', 'Base XP', 'number', '50', 'narrow'], ['category', 'Category', 'select', 'combat|utility|enchant|processing|transport'], ['element', 'Element', 'select', '|bio|eco|hydro|solar|chaos'], ['destination', 'Destination', 'number', '5', 'narrow'], ], inline: [ {label:'Junk Cost', key:'junk_cost', type:'kv_search', searchEntity:'items', 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 = renderRenameableHeader('Module', 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 += renderMoveBar(); html += '
'; html += '
'; html += ''; html += '
'; html += ''; html += ''; html += ''; html += ''; html += '
'; $('#editorMain').innerHTML = html; document.querySelectorAll('details.obj-card').forEach(function(d) { d.addEventListener('toggle', function() { expandedCards[d.getAttribute('data-card')] = d.open; }); }); setupSearchFields(); } function renderCurrent() { ced_syncDOMToData(moduleData); if (!moduleData || !moduleID) return; renderModuleEditor(moduleID, moduleData); } function renderCard(sec, data) { var openAttr = expandedCards[sec.id] === false ? '' : ' open'; var isFull = cardWidths[sec.id] !== undefined ? cardWidths[sec.id] : (sec.fullWidth || false); var fullClass = isFull ? ' obj-card-full' : ''; var h = '
'; h += ''; h += ''; h += '' + sec.label + ''; h += ''; if (!sec.always) { h += ''; } h += ''; 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); } else if (il.type === 'kv_search') { h += ced_renderKVSearch(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 toggleCardWidth(id) { cardWidths[id] = !cardWidths[id]; renderCurrent(); } function removeSection(id) { var sec = MODULE_SECTIONS.find(function(s) { return s.id === id; }); if (!sec) return; ced_syncDOMToData(moduleData); if (sec.path) { delete moduleData[sec.path]; } delete addedSections[id]; renderModuleEditor(moduleID, moduleData); } 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 : ''; }); 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; ced_syncDOMToData(moduleData); var arr = moduleData[sec.path] || []; arr.splice(idx, 1); renderModuleEditor(moduleID, moduleData); } 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' || il.type === 'kv_search') { 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'); }); } function duplicateModule() { var baseID = moduleID + '_copy'; var newID = baseID; var counter = 2; while (true) { var exists = false; for (var i = 0; i < allIDs.length; i++) { if (allIDs[i] === newID) { exists = true; break; } } if (!exists) break; newID = baseID + '_' + counter; counter++; } var copy = JSON.parse(JSON.stringify(moduleData)); delete copy._raw; delete copy._path; copy.id = newID; var dir = ''; if (moduleData._path) { var parts = moduleData._path.split('/'); var parentDir = parts[parts.length - 2]; if (parentDir !== 'modules') dir = parentDir; } API.post('/api/modules', {id: newID}).then(function() { var putOp = API.put('/api/modules/' + encodeURIComponent(newID), copy); if (dir) { return putOp.then(function() { return API.post('/api/modules/move', {id: newID, dir: dir}); }); } return putOp; }).then(function() { notify('Duplicated as ' + newID, 'success'); updateUndoBar(); loadList(); loadModuleEditor(newID); }).catch(function(e) { notify('Duplicate failed: ' + e.message, 'error'); }); }