aboutsummaryrefslogtreecommitdiff
path: root/internal/admin
diff options
context:
space:
mode:
Diffstat (limited to 'internal/admin')
-rw-r--r--internal/admin/static/admin.css4
-rw-r--r--internal/admin/static/intereditor.js168
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 ──