diff options
Diffstat (limited to 'internal/admin')
| -rw-r--r-- | internal/admin/static/admin.css | 4 | ||||
| -rw-r--r-- | internal/admin/static/intereditor.js | 168 |
2 files changed, 114 insertions, 58 deletions
diff --git a/internal/admin/static/admin.css b/internal/admin/static/admin.css index 393fafd..b35d5e2 100644 --- a/internal/admin/static/admin.css +++ b/internal/admin/static/admin.css @@ -400,6 +400,7 @@ g.ud-hover:hover text{font-weight:bold} .ie-cond-leaf input[type="number"], .ie-cond-val{font-size:11px;padding:3px 6px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:3px;font-family:monospace;flex:1;min-width:80px} .ie-cond-subval{font-size:11px;padding:3px 6px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:3px;font-family:monospace;width:100px} +.ie-cond-eq{font-size:11px;color:#888;font-weight:bold;white-space:nowrap;flex-shrink:0} .ie-cond-rm{flex-shrink:0} .ie-cond-grp-rm{margin-left:auto} .ie-cond-addbar{display:flex;gap:3px;flex-wrap:wrap;margin-top:4px} @@ -419,6 +420,9 @@ g.ud-hover:hover text{font-weight:bold} .ie-kv-row{display:flex;gap:4px;align-items:center} .ie-kv-key,.ie-kv-val{font-size:11px;padding:2px 4px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:3px;font-family:monospace;width:120px} .ie-kv-val{width:80px} +.ie-act-msg-delay,.ie-act-msg{font-size:11px;padding:3px 6px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px;font-family:monospace} +.ie-act-msg-delay{width:60px} +.ie-act-msg{flex:1} /* Interaction row layout: column. The single flex header row holds the +Add Required Item / +Add Condition / +Add Action buttons alongside the diff --git a/internal/admin/static/intereditor.js b/internal/admin/static/intereditor.js index b46c456..5cf39a1 100644 --- a/internal/admin/static/intereditor.js +++ b/internal/admin/static/intereditor.js @@ -5,7 +5,7 @@ // ── Condition leaf types ── var IE_COND_TYPES = [ - {type: 'global_flag', label: 'Global Flag', hint: 'world flag name'}, + {type: 'global_flag', label: 'Global Flag', hint: 'global flag name'}, {type: 'player_flag', label: 'Player Flag', hint: 'player flag name'}, {type: 'has_item', label: 'Has Item', hint: 'item ID'}, {type: 'min_credits', label: 'Min Credits', hint: 'amount'}, @@ -218,11 +218,18 @@ function ie_collectAct(rootEl) { if (!rootEl) return null; var r = {}; - // Message (rolled into action) - var msgEl = rootEl.querySelector('.ie-act-msg'); - if (msgEl) { - var msg = msgEl.value.trim(); - r.message = msg; + // Messages — collected from the extendable delay+message rows. + var msgsKv = rootEl.querySelector('.ie-act-kv[data-ie-act-key="messages"]'); + if (msgsKv) { + var list = []; + msgsKv.querySelectorAll('.ie-kv-row').forEach(function(row) { + var delayEl = row.querySelector('.ie-act-msg-delay'); + var msgEl = row.querySelector('.ie-act-msg'); + var d = delayEl ? parseInt(delayEl.value) || 0 : 0; + var m = msgEl ? msgEl.value.trim() : ''; + list.push({ message: m, delay: d }); + }); + r.messages = list; } // KV-type effects — include the map even when empty (as long as the @@ -233,6 +240,7 @@ function ie_collectAct(rootEl) { rootEl.querySelectorAll('.ie-act-kv').forEach(function(kvEl) { var key = kvEl.getAttribute('data-ie-act-key'); if (!key) return; + if (key === 'messages') return; var map = {}; kvEl.querySelectorAll('.ie-kv-row').forEach(function(row) { var kEl = row.querySelector('.ie-kv-key'); @@ -291,8 +299,6 @@ function ie_syncAllInteractions(data, secs) { var act = ie_collectAct(actRoot); if (act) data[p][idx].action = act; else delete data[p][idx].action; - // Clear any legacy top-level message on sync. - delete data[p][idx].message; } }); }); @@ -410,7 +416,7 @@ function _ie_renderLeaf(leafType, leafVal, leafSub, not, secId, secPath, idx, gp var h = '<div class="ie-cond-leaf" data-ie-leaftype="' + leafType + '">'; h += '<span class="ie-cond-leaf-label">' + esc(label) + '</span>'; if (isSearch) { - h += '<div style="position:relative;min-width:0;flex:1">'; + h += '<div class="obj-search" style="position:relative;min-width:0;flex:1">'; h += '<input class="ie-cond-val search-input" data-search-type="items" placeholder="' + escAttr(hint) + '" value="' + escAttr(sv) + '">'; h += '<div class="search-results" style="display:none"></div>'; h += '</div>'; @@ -420,7 +426,8 @@ function _ie_renderLeaf(leafType, leafVal, leafSub, not, secId, secPath, idx, gp h += '<input class="ie-cond-val" placeholder="' + escAttr(hint) + '" value="' + escAttr(sv) + '">'; } if (leafType === 'player_flag' || leafType === 'global_flag') { - h += '<input class="ie-cond-subval" placeholder="Value (optional)" value="' + escAttr(ssv) + '">'; + h += '<span class="ie-cond-eq">=</span>'; + h += '<input class="ie-cond-subval" placeholder="exists" value="' + escAttr(ssv) + '">'; } h += '<label class="ie-cond-notlbl"><input type="checkbox" class="ie-cond-not" onchange="ie_toggleNotLeaf(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(gpath) + '\',' + ciToken + ')"' + (not ? ' checked' : '') + '> NOT</label>'; h += '<button class="btn btn-sm btn-danger ie-cond-rm" onclick="ie_removeCond(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(gpath) + '\',' + ciToken + ')" title="Remove">X</button>'; @@ -434,14 +441,32 @@ function ie_renderAct(actionObj, scope, secId, idx, secPath) { var hasAny = false; var rows = ''; - // Message - var msg = (actionObj && actionObj.message !== undefined && actionObj.message !== null) ? String(actionObj.message) : ''; - var msgPresent = actionObj && Object.prototype.hasOwnProperty.call(actionObj, 'message'); - if (msgPresent) { + // Messages — extendable list of {delay, message} entries. Mirrors the KV-section + // pattern: when the 'messages' key is present we render the section with rows + // and an internal + Add; when it goes away (last row removed) the section + // disappears and the addbar + Add Message button reappears. There is no + // section-level X. + var msgsPresent = actionObj && Object.prototype.hasOwnProperty.call(actionObj, 'messages') && Array.isArray(actionObj.messages); + if (msgsPresent) { + var msgs = actionObj.messages; hasAny = true; - rows += '<div class="ie-act-row"><span class="ie-act-label">Message</span>'; - rows += '<input class="ie-act-input ie-act-msg" data-ie-act-key="message" placeholder="Shown to player" value="' + escAttr(msg) + '" style="flex:1">'; - rows += '<button class="btn btn-sm btn-danger ie-act-rm" onclick="ie_removeActEffect(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'message\')">X</button>'; + var rmFn = 'ie_removeMessage(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ')'; + rows += '<div class="ie-act-kv" data-ie-act-key="messages">'; + rows += '<div class="ie-act-kv-header"><span>Messages</span></div>'; + rows += '<div class="ie-kv-list">'; + if (msgs.length === 0) { + rows += '<div class="ie-kv-row"><input type="number" class="ie-act-msg-delay" value="" placeholder="delay" style="width:60px" title="Ticks before showing">'; + rows += '<input class="ie-act-msg" data-ie-msg-idx="0" value="" placeholder="Shown to player" style="flex:1">'; + rows += '<button class="btn btn-sm btn-danger" onclick="' + rmFn + '">X</button></div>'; + } else { + msgs.forEach(function(m, mi) { + rows += '<div class="ie-kv-row"><input type="number" class="ie-act-msg-delay" value="' + escAttr(m.delay ? String(m.delay) : '') + '" placeholder="delay" style="width:60px" title="Ticks before showing">'; + rows += '<input class="ie-act-msg" data-ie-msg-idx="' + mi + '" value="' + escAttr(String(m.message || '')) + '" placeholder="Shown to player" style="flex:1">'; + rows += '<button class="btn btn-sm btn-danger" onclick="' + rmFn + '">X</button></div>'; + }); + } + rows += '</div>'; + rows += '<button class="btn btn-sm ie-add-btn" style="margin-top:2px" onclick="ie_addMessage(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ')">+ Add Message</button>'; rows += '</div>'; } @@ -477,12 +502,12 @@ function ie_renderAct(actionObj, scope, secId, idx, secPath) { rows += '</div>'; } else if (at.kind === 'checkbox') { rows += '<div class="ie-act-row"><span class="ie-act-label">' + esc(at.label) + '</span>'; - rows += '<label><input type="checkbox" class="ie-act-input" data-ie-act-key="' + escAttr(at.key) + '" data-ie-act-kind="checkbox" checked> Enabled</label>'; + rows += '<input type="checkbox" class="ie-act-input" data-ie-act-key="' + escAttr(at.key) + '" data-ie-act-kind="checkbox" checked hidden>'; rows += '<button class="btn btn-sm btn-danger ie-act-rm" onclick="ie_removeActEffect(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(at.key) + '\')">X</button>'; rows += '</div>'; } else if (at.kind === 'search') { rows += '<div class="ie-act-row"><span class="ie-act-label">' + esc(at.label) + '</span>'; - rows += '<div style="position:relative;min-width:0;flex:1"><input class="ie-act-input search-input" data-ie-act-key="' + escAttr(at.key) + '" data-search-type="' + escAttr(at.entity) + '" placeholder="' + escAttr(at.hint || 'Search ' + at.entity + '...') + '" value="' + escAttr(String(val)) + '"><div class="search-results" style="display:none"></div></div>'; + rows += '<div class="obj-search" style="position:relative;min-width:0;flex:1"><input class="ie-act-input search-input" data-ie-act-key="' + escAttr(at.key) + '" data-search-type="' + escAttr(at.entity) + '" placeholder="' + escAttr(at.hint || 'Search ' + at.entity + '...') + '" value="' + escAttr(String(val)) + '"><div class="search-results" style="display:none"></div></div>'; rows += '<button class="btn btn-sm btn-danger ie-act-rm" onclick="ie_removeActEffect(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(at.key) + '\')">X</button>'; rows += '</div>'; } else { @@ -503,7 +528,7 @@ function ie_renderAct(actionObj, scope, secId, idx, secPath) { if (at.kind === 'search') { rows += '<div class="ie-act-row"><span class="ie-act-label">' + esc(at.label) + '</span>'; - rows += '<div style="position:relative;min-width:0;flex:1"><input class="ie-act-input search-input" data-ie-act-key="' + escAttr(at.key) + '" data-search-type="' + escAttr(at.entity) + '" placeholder="' + escAttr(at.hint || 'Search ' + at.entity + '...') + '" value="' + escAttr(String(val)) + '"><div class="search-results" style="display:none"></div></div>'; + rows += '<div class="obj-search" style="position:relative;min-width:0;flex:1"><input class="ie-act-input search-input" data-ie-act-key="' + escAttr(at.key) + '" data-search-type="' + escAttr(at.entity) + '" placeholder="' + escAttr(at.hint || 'Search ' + at.entity + '...') + '" value="' + escAttr(String(val)) + '"><div class="search-results" style="display:none"></div></div>'; rows += '<button class="btn btn-sm btn-danger ie-act-rm" onclick="ie_removeActEffect(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(at.key) + '\')">X</button>'; rows += '</div>'; } else { @@ -517,8 +542,8 @@ function ie_renderAct(actionObj, scope, secId, idx, secPath) { // Add effect buttons var addbar = '<div class="ie-act-addbar">'; - if (!msgPresent) { - addbar += '<button class="btn btn-sm ie-add-btn" onclick="ie_addActEffect(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'message\',\'' + escAttr(scope) + '\')">+ Add Message</button>'; + if (!msgsPresent) { + addbar += '<button class="btn btn-sm ie-add-btn" onclick="ie_addActEffect(\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',\'messages\',\'' + escAttr(scope) + '\')">+ Add Message</button>'; } IE_ACT_TYPES.forEach(function(at) { if (actionObj && Object.prototype.hasOwnProperty.call(actionObj, at.key)) { @@ -754,18 +779,6 @@ function ie_toggleCondNot(secId, secPath, idx, groupPath) { var ed = window._editorData; if (!ed) return; ie_syncCondEntry(ed, secId, secPath, idx); - var cond = (ed[secPath] && ed[secPath][idx]) ? ed[secPath][idx].condition : null; - var group = ie_condAt(cond, groupPath); - if (!group) return; - - if (group.all_of || group.any_of) { - group.not = !group.not; - if (!group.not) delete group.not; - var nc = ie_condReplace(cond, groupPath, group); - if (!ed[secPath][idx]) ed[secPath][idx] = {}; - if (nc) ed[secPath][idx].condition = nc; - else delete ed[secPath][idx].condition; - } ced_syncDOMToData(ed); window._ieRenderOnly(); } @@ -774,26 +787,6 @@ function ie_toggleNotLeaf(secId, secPath, idx, groupPath, ci) { var ed = window._editorData; if (!ed) return; ie_syncCondEntry(ed, secId, secPath, idx); - var cond = (ed[secPath] && ed[secPath][idx]) ? ed[secPath][idx].condition : null; - var parent = ie_condAt(cond, groupPath); - if (!parent) return; - - var leaf; - if (ci === IE_ROOT_LEAF) { - leaf = parent; - } else { - if (!parent.all_of && !parent.any_of) { - leaf = parent; - } else { - var children = parent.all_of || parent.any_of; - if (typeof ci !== 'number' || ci < 0 || ci >= children.length) return; - leaf = children[ci]; - } - } - - leaf.not = !leaf.not; - if (!leaf.not) delete leaf.not; - ed[secPath][idx].condition = cond; ced_syncDOMToData(ed); window._ieRenderOnly(); } @@ -809,8 +802,8 @@ function ie_addActEffect(secId, secPath, idx, effectKey, scope) { if (!ed[secPath][idx].action) ed[secPath][idx].action = {}; var act = ed[secPath][idx].action; - if (effectKey === 'message') { - act.message = ''; + if (effectKey === 'messages') { + act.messages = []; } else { var at = IE_ACT_TYPES.find(function(a) { return a.key === effectKey; }); var at2 = IE_ACT_TYPES_SEQ.find(function(a) { return a.key === effectKey; }); @@ -898,6 +891,67 @@ function ie_removeKVRow(rowEl, secId, secPath, idx, effectKey) { window._ieRenderOnly(); } +// ── Add/Remove: Messages list row ── + +// ie_addMessage appends a blank delay+message row to the DOM (no data change +// yet — it joins the list on the next sync, the same way typing into an +// existing row does). The Messages section must already be present (created +// via + Add Message in the addbar). Focuses the new row's message input. +function ie_addMessage(secId, secPath, idx) { + var ed = window._editorData; + if (!ed) return; + ie_syncActEntry(ed, secId, secPath, idx); + var card = document.querySelector('.obj-card[data-card="' + secId + '"]'); + if (!card) return; + var rows = card.querySelectorAll('.obj-sub-row'); + var row = rows[idx]; + if (!row) return; + var list = row.querySelector('.ie-act-kv[data-ie-act-key="messages"] .ie-kv-list'); + if (!list) return; + var rowEl = document.createElement('div'); + rowEl.className = 'ie-kv-row'; + var rmFn = 'ie_removeMessage(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ')'; + rowEl.innerHTML = '<input type="number" class="ie-act-msg-delay" value="" placeholder="delay" style="width:60px" title="Ticks before showing"><input class="ie-act-msg" value="" placeholder="Shown to player" style="flex:1"><button class="btn btn-sm btn-danger" onclick="' + rmFn + '">X</button>'; + list.appendChild(rowEl); + rowEl.querySelector('.ie-act-msg').focus(); +} + +// ie_removeMessage removes a message entry from the action's messages list. If +// the list becomes empty the entire messages key is dropped (and the section +// disappears on re-render). rowEl may be the X button itself. +function ie_removeMessage(rowEl, secId, secPath, idx) { + var ed = window._editorData; + if (!ed) return; + if (rowEl && rowEl.closest) { + var kvRow = rowEl.closest('.ie-kv-row'); + if (kvRow) rowEl = kvRow; + } + ie_syncActEntry(ed, secId, secPath, idx); + if (!ed[secPath] || !ed[secPath][idx] || !ed[secPath][idx].action) return; + var act = ed[secPath][idx].action; + if (!act.hasOwnProperty('messages') || !Array.isArray(act.messages)) return; + + // Find which row index to remove by locating our row in the list. + var card = document.querySelector('.obj-card[data-card="' + secId + '"]'); + var subRows = card ? card.querySelectorAll('.obj-sub-row') : []; + var row = subRows[idx]; + if (row) { + var kvRows = row.querySelectorAll('.ie-act-kv[data-ie-act-key="messages"] .ie-kv-row'); + for (var ri = 0; ri < kvRows.length; ri++) { + if (kvRows[ri] === rowEl || kvRows[ri].contains(rowEl)) { + act.messages.splice(ri, 1); + break; + } + } + } + if (act.messages.length === 0) { + delete act.messages; + if (Object.keys(act).length === 0) delete ed[secPath][idx].action; + } + ced_syncDOMToData(ed); + window._ieRenderOnly(); +} + // ── Pre-render sync helpers ── function ie_syncCondEntry(ed, secId, secPath, idx) { @@ -940,8 +994,6 @@ function ie_syncActEntry(ed, secId, secPath, idx) { } else { delete ed[secPath][idx].action; } - // Clear any legacy top-level message on sync. - delete ed[secPath][idx].message; } // ── Shared interaction-style array-section renderer ── |
