From 21c1997a3fd0f5ec5dcff1add45e4dfaf7a338ca Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Sun, 5 Jul 2026 03:45:48 -0400 Subject: fix: use interaction cards in admin gui work, fix card css on object tab --- internal/admin/static/admin.css | 6 +- internal/admin/static/cardeditor.js | 41 ++++++- internal/admin/static/itemeditor.js | 15 ++- internal/admin/static/mobeditor.js | 4 +- internal/admin/static/objecteditor.js | 216 ++++++++++++++++++++++++++++------ 5 files changed, 241 insertions(+), 41 deletions(-) (limited to 'internal/admin/static') diff --git a/internal/admin/static/admin.css b/internal/admin/static/admin.css index 33dbdc4..3c6aaa4 100644 --- a/internal/admin/static/admin.css +++ b/internal/admin/static/admin.css @@ -277,9 +277,9 @@ g.ud-hover:hover text{font-weight:bold} .obj-card-header:hover{background:rgba(15,52,96,.5)} .obj-card-arrow{font-size:10px;width:14px;color:#888} .obj-card-label{flex:1;color:var(--text)} -.obj-card-remove{padding:2px 6px;font-size:10px;min-width:auto} -.obj-card-expand{padding:2px 5px;font-size:11px;min-width:auto;background:transparent;color:#888;border:1px solid transparent;border-radius:3px;cursor:pointer;font-family:monospace;line-height:1} -.obj-card-expand:hover{color:var(--text);border-color:var(--border);background:rgba(255,255,255,.05)} +.obj-card-remove{width:18px;height:18px;padding:0;display:inline-flex;align-items:center;justify-content:center;font-size:10px;min-width:auto;flex-shrink:0} +.obj-card-expand{width:18px;height:18px;padding:0;display:inline-flex;align-items:center;justify-content:center;font-size:9px;min-width:auto;background:rgba(255,255,255,0.07);color:#888;border:1px solid rgba(255,255,255,0.12);border-radius:3px;cursor:pointer;font-family:monospace;line-height:1;flex-shrink:0} +.obj-card-expand:hover{color:var(--text);border-color:var(--border);background:rgba(255,255,255,0.12)} .obj-card-body{padding:10px 12px} .obj-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:6px 8px} .obj-field{display:flex;flex-direction:column;gap:2px;font-size:11px} diff --git a/internal/admin/static/cardeditor.js b/internal/admin/static/cardeditor.js index 0235894..f2316e1 100644 --- a/internal/admin/static/cardeditor.js +++ b/internal/admin/static/cardeditor.js @@ -166,12 +166,42 @@ function ced_selectSearchResult(el) { var idx = Array.prototype.indexOf.call(results.children, el); if (results._items && results._items[idx] && results._targetInput) { results._targetInput.value = results._items[idx].id; + ced_persistSearchToData(results._targetInput, results._items[idx].id); results._targetInput.dispatchEvent(new Event('input')); results.style.display = 'none'; results.innerHTML = ''; } } +function ced_persistSearchToData(input, value) { + var ed = window._editorData; + var dataPath = input.getAttribute('data-data-path'); + if (!ed || !dataPath) return; + var m = dataPath.match(/^(.+)\[(\d+)\]\.(.+)$/); + if (m) { + var arr = ced_resolveDataPath(ed, m[1]); + if (arr && Array.isArray(arr)) { + if (!arr[m[2]]) arr[m[2]] = {}; + arr[m[2]][m[3]] = value; + } + } else { + var m2 = dataPath.match(/^(.+)\.(.+)$/); + if (m2) { + var obj = ced_resolveDataPath(ed, m2[1]); + if (obj) obj[m2[2]] = value; + } else { + ed[dataPath] = value; + } + } +} + +function ced_resolveDataPath(obj, path) { + return path.split('.').reduce(function(o, k) { + if (o === undefined || o === null) return undefined; + return o[k] !== undefined ? o[k] : undefined; + }, obj); +} + function ced_updateSearchHighlight(results) { for (var i = 0; i < results.children.length; i++) { results.children[i].classList.toggle('active', i === results._selectedIndex); @@ -240,7 +270,16 @@ function ced_renderField(sec, f, data, idx, subPath, optStyle, cardPathFn, field if (type === 'search') { var entity = searchEntity || 'items'; var ph = hint ? ' placeholder="' + esc(hint) + '"' : ' placeholder="Search ' + entity + '..."'; - return wrap('obj-field obj-search', '' + esc(label) + '
'); + var pfx = cardPathFn(sec); + var dp; + if (idx >= 0) { + dp = pfx + '[' + idx + '].' + key; + } else if (subPath) { + dp = (pfx ? pfx + '.' : '') + subPath + '.' + key; + } else { + dp = (pfx ? pfx + '.' : '') + key; + } + return wrap('obj-field obj-search', '' + esc(label) + '
'); } var ph = hint ? ' placeholder="' + esc(hint) + '"' : ''; return wrap('obj-field', '' + esc(label) + ''); diff --git a/internal/admin/static/itemeditor.js b/internal/admin/static/itemeditor.js index f1450ab..5a2b371 100644 --- a/internal/admin/static/itemeditor.js +++ b/internal/admin/static/itemeditor.js @@ -166,6 +166,7 @@ function loadItemEditor(id) { window.location.hash = id; API.get('/api/items/' + encodeURIComponent(id)).then(function(data) { itemData = data; + window._editorData = itemData; renderItemEditor(id, data); }).catch(function(e) { $('#editorMain').innerHTML = '

Failed to load: ' + esc(e.message) + '

'; @@ -524,7 +525,16 @@ function renderField(sec, f, data, idx, subPath, optStyle) { if (type === 'search') { var entity = searchEntity || 'items'; var ph = hint ? ' placeholder="' + esc(hint) + '"' : ' placeholder="Search ' + entity + '..."'; - return wrap('obj-field obj-search', '' + esc(label) + '
'); + var pfx = cardPath(sec); + var dp; + if (idx >= 0) { + dp = pfx + '[' + idx + '].' + key; + } else if (subPath) { + dp = (pfx ? pfx + '.' : '') + subPath + '.' + key; + } else { + dp = (pfx ? pfx + '.' : '') + key; + } + return wrap('obj-field obj-search', '' + esc(label) + '
'); } var ph = hint ? ' placeholder="' + esc(hint) + '"' : ''; return wrap('obj-field', '' + esc(label) + ''); @@ -568,7 +578,8 @@ function renderSubFieldHTML(sec, stKey, idx, f, val) { if (f[2] === 'search') { var entity = f[6] || 'items'; var ph = f[3] ? ' placeholder="' + escAttr(f[3]) + '"' : ' placeholder="Search ' + entity + '..."'; - return ''; + var dp = cardPath(sec) + '.' + stKey + '[' + idx + '].' + f[0]; + return ''; } if (f[2] === 'checkbox') { return ''; diff --git a/internal/admin/static/mobeditor.js b/internal/admin/static/mobeditor.js index 1758673..c564088 100644 --- a/internal/admin/static/mobeditor.js +++ b/internal/admin/static/mobeditor.js @@ -138,6 +138,7 @@ function loadMob(id) { renderList(''); API.get('/api/mobs/' + encodeURIComponent(id)).then(function(data) { mobData = data; + window._editorData = mobData; if (mobData.shop && mobData.shop.buys_anything === undefined) { mobData.shop.buys_anything = true; } @@ -427,7 +428,8 @@ function renderMobSubField(sec, stKey, idx, f, val) { if (f[2] === 'search') { var entity = f[6] || 'items'; var ph = f[3] ? ' placeholder="' + escAttr(f[3]) + '"' : ' placeholder="Search ' + entity + '..."'; - return ''; + var dp = mobCardPath(sec) + '.' + stKey + '[' + idx + '].' + f[0]; + return ''; } if (f[2] === 'checkbox') { return ''; diff --git a/internal/admin/static/objecteditor.js b/internal/admin/static/objecteditor.js index 5153458..c80cb57 100644 --- a/internal/admin/static/objecteditor.js +++ b/internal/admin/static/objecteditor.js @@ -19,7 +19,7 @@ var SECTIONS = [ }, { id: 'steal', label: 'Steal', - detect: function(d) { return d.steal_table || d.steal_level || d.steal_speed || d.steal_xp || d.guard_mob; }, + detect: function(d) { return d.steal_table !== undefined || d.steal_level !== undefined || d.steal_speed !== undefined || d.steal_xp !== undefined || d.guard_mob !== undefined; }, fields: [ ['steal_table', 'Table ID', 'text'], ['steal_level', 'Level', 'number', '', 'narrow'], @@ -89,7 +89,7 @@ var SECTIONS = [ detect: function(d) { return d.safespot; }, fields: [ ['tier', 'Tier', 'number', '', 'narrow'], - ['max_block_size', 'Max Block Size', 'text'], + ['max_block_size', 'Max Block Size', 'select', '|small|medium|large|massive'], ['max_occupants', 'Max Occupants', 'number', '', 'narrow'], ['unsafe_chance', 'Unsafe Chance', 'number', '', 'narrow'], ['decay_ticks', 'Decay Ticks', 'number', '', 'narrow'], @@ -104,13 +104,13 @@ var SECTIONS = [ ] }, { - id: 'use_interactions', label: 'Use Interactions', path: 'use_interactions', isArray: true, + id: 'use_interactions', label: 'Use Interactions', labelHint: '(what happens when you use items on this)', path: 'use_interactions', isArray: true, fullWidth: true, detect: function(d) { return d.use_interactions && Array.isArray(d.use_interactions); }, fields: [ - ['item_id', 'Item ID', 'text'], + ['item_id', 'Item ID', 'search', '', '', null, 'items'], + ['condition', 'Condition', 'json', 'e.g. {"flag":"my_flag"} or {"player_flag":"x","value":1} or {"all_of":[{"flag":"a"},{"has_item":"key"}]}'], + ['action', 'Action', 'json', 'e.g. {"set_flags":{"my_flag":true},"message":"You activate it.","give_item":"gem"}'], ['message', 'Message', 'text'], - ['condition', 'Condition', 'json'], - ['action', 'Action', 'json'], ] }, { @@ -145,6 +145,9 @@ function loadObject(id) { renderList(''); API.get('/api/objects/' + encodeURIComponent(id)).then(function(data) { objectData = data; + window._editorData = objectData; + window._useInterVis = {}; + window._useInterHelpCollapsed = undefined; renderObjectEditor(id, data); }).catch(function(e) { $('#editorMain').innerHTML = '

Failed to load: ' + esc(e.message) + '

'; @@ -230,7 +233,11 @@ function renderCard(sec, data) { var h = '
'; h += '
'; h += '' + (collapsed ? '▶' : '▼') + ''; - h += '' + sec.label + ''; + h += '' + sec.label; + if (sec.labelHint) { + h += ' ' + esc(sec.labelHint) + ''; + } + h += ''; h += ''; if (!sec.always) { h += ''; @@ -269,9 +276,7 @@ function renderCard(sec, data) { h += renderTags(sec, il, data); h += '
'; } else if (il.type === 'tools_checkbox') { - h += '
'; h += renderToolsCheckbox(sec, il, data); - h += '
'; } else if (il.type === 'kv') { h += '
'; h += renderKV(sec, il, data); @@ -368,7 +373,7 @@ function renderField(sec, f, data, idx, subPath, optStyle) { var opts = hint ? hint.split('|') : []; var inner = '' + esc(label) + ''; return wrap('obj-field', inner); @@ -380,7 +385,16 @@ function renderField(sec, f, data, idx, subPath, optStyle) { if (type === 'search') { var entity = searchEntity || 'items'; var ph = hint ? ' placeholder="' + esc(hint) + '"' : ' placeholder="Search ' + entity + '..."'; - return wrap('obj-field obj-search', '' + esc(label) + '
'); + var pfx = cardPath(sec); + var dp; + if (idx >= 0) { + dp = pfx + '[' + idx + '].' + key; + } else if (subPath) { + dp = (pfx ? pfx + '.' : '') + subPath + '.' + key; + } else { + dp = (pfx ? pfx + '.' : '') + key; + } + return wrap('obj-field obj-search', '' + esc(label) + '
'); } var ph = hint ? ' placeholder="' + esc(hint) + '"' : ''; return wrap('obj-field', '' + esc(label) + ''); @@ -405,17 +419,92 @@ function renderCoreCard(data) { function renderArraySection(sec, data) { var arr = data[cardPath(sec)] || []; var h = ''; + var isUseInter = sec.id === 'use_interactions'; + if (isUseInter) { + window._useInterVis = window._useInterVis || {}; + } arr.forEach(function(item, idx) { - h += '
'; - h += '
'; - sec.fields.forEach(function(f) { - h += renderField(sec, f, data, idx); - }); - h += '
'; - h += ''; - h += '
'; + if (isUseInter) { + var vis = window._useInterVis[idx] || {}; + window._useInterVis[idx] = vis; + var showCond = vis.condition || (item.condition && item.condition !== ''); + var showAct = vis.action || (item.action && item.action !== ''); + var showMsg = vis.message || (item.message && item.message !== ''); + + h += '
'; + + h += '
'; + h += renderField(sec, sec.fields[0], data, idx, null, 'max-width:260px'); + h += '
'; + if (!showCond) { + h += ''; + } + if (!showAct) { + h += ''; + } + if (!showMsg) { + h += ''; + } + h += ''; + h += '
'; + + h += '
'; + h += renderField(sec, sec.fields[1], data, idx, null, 'flex:1;min-width:0'); + h += ''; + h += '
'; + + h += '
'; + h += renderField(sec, sec.fields[2], data, idx, null, 'flex:1;min-width:0'); + h += ''; + h += '
'; + + h += '
'; + h += renderField(sec, sec.fields[3], data, idx, null, 'flex:1;min-width:0'); + h += ''; + h += '
'; + + h += '
'; + } else { + h += '
'; + h += '
'; + sec.fields.forEach(function(f) { + h += renderField(sec, f, data, idx); + }); + h += '
'; + h += ''; + h += '
'; + } }); h += ''; + if (isUseInter) { + var helpCollapsed = window._useInterHelpCollapsed === undefined ? true : window._useInterHelpCollapsed; + h += '
'; + h += '
'; + h += '' + (helpCollapsed ? '▶' : '▼') + ''; + h += 'How Conditions & Actions Work'; + h += '
'; + if (!helpCollapsed) { + h += '
'; + h += 'Entries checked top-to-bottom, first match wins.

Item ID: required for use <item> on <object>; omit for plain use <object>.
'; + h += 'Condition: JSON. When this interaction is available:
'; + h += '  {"flag":"gate_open","not":true} — world flag is NOT set
'; + h += '  {"player_flag":"has_key","value":1} — player flag matches value
'; + h += '  {"has_item":"silver_bar"} — player carries item
'; + h += '  {"all_of":[{"flag":"a"},{"has_item":"b"}]} — ALL sub-conditions
'; + h += '  {"any_of":[...]} — ANY sub-condition
'; + h += '  Omit for an unconditional interaction.
'; + h += 'Action: JSON. What happens when the interaction fires:
'; + h += '  {"set_flags":{"gate_open":true}} — set world flags
'; + h += '  {"set_player_flags":{"spoke":true}} — set player flags
'; + h += '  {"give_item":"keycard","teleport":5} — give item + teleport
'; + h += '  {"take_item":"bomb","heal":10} — take item + heal
'; + h += '  {"credits":-50} — deduct credits
'; + h += '  {"reputation_cost":1,"aps_node":true} — rep cost + mark APS
'; + h += 'Message: Sent to the player when the interaction fires (1-tick action).'; + h += '
'; + } + h += '
'; + } return h; } @@ -431,8 +520,11 @@ function renderSubtable(sec, st, data) { var h = '
'; h += '
' + esc(st.label) + '
'; arr.forEach(function(item, idx) { - h += '
'; + h += '
'; if (isDrops) { + if (!st.single) { + h += ''; + } var isItemDrop = item._type !== undefined ? item._type !== 't' : !item.table; var showField = isItemDrop ? 'item_id' : 'table'; var hideField = isItemDrop ? 'table' : 'item_id'; @@ -448,16 +540,14 @@ function renderSubtable(sec, st, data) { if (f[2] === 'search') { var entity = f[5] || 'items'; var ph = f[3] ? ' placeholder="' + escAttr(f[3]) + '"' : ' placeholder="Search ' + entity + '..."'; - h += ''; + var dp = cardPath(sec) + '.' + st.key + '[' + idx + '].' + f[0]; + h += ''; } else if (f[2] === 'checkbox') { h += ''; } else { h += ''; } }); - if (!st.single) { - h += ''; - } h += '
'; h += '
'; var fidMsg = fieldIDSub(sec, st.key, idx, 'message'); @@ -476,7 +566,8 @@ function renderSubtable(sec, st, data) { if (f[2] === 'search') { var entity = f[5] || 'items'; var ph = f[3] ? ' placeholder="' + escAttr(f[3]) + '"' : ' placeholder="Search ' + entity + '..."'; - h += ''; + var dp = cardPath(sec) + '.' + st.key + '[' + idx + '].' + f[0]; + h += ''; } else if (f[2] === 'checkbox') { h += ''; } else { @@ -530,21 +621,52 @@ function renderToolsCheckbox(sec, il, data) { var sectionRoot = data[cardPath(sec)] || {}; var arr = sectionRoot[il.key] || []; var choices = toolTypes || []; + if (choices.length === 0) { + return 'Loading tool types...'; + } + if (arr.length === 0) { + return ''; + } var h = '
'; h += '' + esc(il.label) + ''; - h += '
'; - choices.forEach(function(t) { - var checked = arr.indexOf(t) >= 0 ? ' checked' : ''; - h += ''; + h += '
'; + arr.forEach(function(t, idx) { + h += '
'; + h += ''; + h += ''; + h += '
'; }); h += '
'; - if (choices.length === 0) { - h += 'Loading tool types...'; - } + h += ''; h += '
'; return h; } +function addToolRow(secID, key) { + var sec = SECTIONS.find(function(s) { return s.id === secID; }); + if (!sec) return; + var p = cardPath(sec); + if (!objectData[p]) objectData[p] = {}; + objectData[p][key] = (objectData[p][key] || []).concat(['']); + renderCurrent(); +} + +function removeToolRow(secID, key, idx) { + var sec = SECTIONS.find(function(s) { return s.id === secID; }); + if (!sec) return; + var p = cardPath(sec); + var arr = objectData[p] && objectData[p][key]; + if (!arr) return; + arr.splice(idx, 1); + if (arr.length === 0) delete objectData[p][key]; + renderCurrent(); +} + function fetchToolTypes() { API.get('/api/tools').then(function(tools) { toolTypes = tools || []; @@ -688,6 +810,32 @@ function renderCurrent() { renderObjectEditor(objectID, objectData); } +function showUseInterField(idx, key) { + window._useInterVis = window._useInterVis || {}; + if (!window._useInterVis[idx]) window._useInterVis[idx] = {}; + window._useInterVis[idx][key] = true; + renderCurrent(); +} + +function hideUseInterField(idx, key) { + window._useInterVis = window._useInterVis || {}; + if (!window._useInterVis[idx]) window._useInterVis[idx] = {}; + window._useInterVis[idx][key] = false; + if (objectData.use_interactions && objectData.use_interactions[idx]) { + objectData.use_interactions[idx][key] = ''; + } + renderCurrent(); +} + +function toggleUseInterHelp() { + if (window._useInterHelpCollapsed === undefined) { + window._useInterHelpCollapsed = false; + } else { + window._useInterHelpCollapsed = !window._useInterHelpCollapsed; + } + renderCurrent(); +} + function toggleGatherConditionals() { var skillEl = document.getElementById('f_gather_skill'); var skill = skillEl ? skillEl.value : ''; @@ -780,9 +928,9 @@ function saveObject() { hasValues = true; } } else if (il.type === 'tools_checkbox') { - var chks = document.querySelectorAll('.obj-tool-chk:checked'); + var selects = document.querySelectorAll('.obj-tool-select'); var tools = []; - chks.forEach(function(c) { tools.push(c.getAttribute('data-tool')); }); + selects.forEach(function(s) { if (s.value) tools.push(s.value); }); if (tools.length > 0) { sectionObj[il.key] = tools; hasValues = true; } } else if (il.type === 'kv') { var kvEl = document.querySelector('.obj-kv-list[data-sec="' + sec.id + '"][data-key="' + il.key + '"]'); -- cgit v1.2.3